- All 6 implementation tasks completed (Tasks 1-6) - All 4 verification tasks completed (F1-F4) - All 4 Definition of Done items verified - All 6 Final Checklist items verified - Total: 20/20 tasks complete Summary: - 182/182 tests pass - Build succeeds - 16/16 QA scenarios pass - All user-reported items fixed - Ready for production deployment
38 KiB
CTS Presenter App: Bug Fixes & Feature Additions
TL;DR
Quick Summary: Fix 4 bugs (file upload crash, sermon block not wired, sync error swallowed, missing refreshPage) and implement 3 features (side-by-side upload layout, archived services toggle, API request logging with frontend UI).
Deliverables:
- Working file uploads in all slide blocks (information, moderation, sermon)
- Sermon block fully wired in service edit page
- Sync error messages visible to user with actual error details
- Upload area repositioned to the right of the slides grid
- Toggle switch for archived/past services view
- New API request log page with search, filter, and error highlighting
Estimated Effort: Medium Parallel Execution: YES - 2 waves Critical Path: Bug 2+3 (upload fix) -> Feature 5 (layout) ; Bug 1 (sync) -> Feature 7 (API log)
Context
Original Request
User reported 7 items: upload area layout change, archived services toggle, API request logging, sync error, upload failures (information + moderation JPG), second upload JS error, and sermon slides disabled.
Interview Summary
Key Discussions:
- All code was explored in previous session with root causes identified
- Metis review revealed SermonBlock.vue ALREADY EXISTS (was incorrectly assumed missing)
- Metis discovered additional bug: refreshPage function undefined in Edit.vue
Research Findings:
- Vue3Dropzone wraps File objects as {file: File, id: number} - confirmed from source code
- SlideUploader.vue line 78 accesses file.name directly (should be file.file.name)
- Edit.vue emits slides-updated to refreshPage but no refreshPage function is defined
- SyncController only checks exit code, losing the actual error message
Metis Review
Identified Gaps (addressed):
- SermonBlock.vue already exists - plan corrected to only wire it in Edit.vue, NOT create it
- refreshPage function missing in Edit.vue - added as part of Bug 4 fix
- Sync error message is swallowed - fix includes propagating actual error to frontend
- API request log scope clarified: log high-level method calls, not raw HTTP
Work Objectives
Core Objective
Fix all broken functionality (uploads, sync, sermon block) and add three requested features (layout, archived toggle, API log).
Concrete Deliverables
- SlideUploader.vue - fixed file object access for Vue3Dropzone compatibility
- Services/Edit.vue - SermonBlock wired + refreshPage function added
- SyncController.php - actual error messages propagated to frontend
- InformationBlock.vue, ModerationBlock.vue, SermonBlock.vue - side-by-side layout
- Services/Index.vue + ServiceController.php - archived toggle
- New ApiRequestLog model, migration, controller, and ApiLogs/Index.vue page
Definition of Done
- All 174 Pest tests pass:
cd /Users/thorsten/AI/cts-work && php artisan test - All 83 E2E Playwright tests pass:
cd /Users/thorsten/AI/cts-work && npx playwright test npm run buildcompletes without errors- All 7 user-reported items verified working
Must Have
- File uploads work in Information, Moderation, and Sermon blocks
- Sermon block renders with uploader and grid (not placeholder)
- Sync errors show actual error message, not generic text
- Archived services toggle on Services index page
- Upload area smaller and to the right of slides grid
- API request log page accessible from navigation
- All text in German (Du, not Sie)
Must NOT Have (Guardrails)
- MUST NOT write to CTS API - all API interactions are READ-ONLY
- MUST NOT create a new SermonBlock.vue - it already exists at resources/js/Components/Blocks/SermonBlock.vue
- MUST NOT change SlideController.php backend - it is correct, fix is frontend-only
- MUST NOT modify Vue3Dropzone library code in node_modules
- MUST NOT restructure ChurchToolsService sync logic - only wrap/decorate for logging
- MUST NOT add retry logic, fallback behaviors, or complex error recovery to sync
- MUST NOT add date range filters, search, or advanced sorting to archived view - binary toggle only
- MUST NOT add charts, alerts, export, or dashboards to API log - simple filterable table only
- MUST NOT fix unrelated issues discovered during exploration
- MUST NOT break mobile responsiveness when changing upload layout
Verification Strategy
ZERO HUMAN INTERVENTION - ALL verification is agent-executed.
Test Decision
- Infrastructure exists: YES (174 Pest tests, 83 Playwright E2E tests)
- Automated tests: Tests-after (add tests for new features, verify existing pass)
- Framework: Pest (PHP), Playwright (E2E)
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: Use Bash (curl / artisan) - Run commands, assert output
- Full Suite:
cd /Users/thorsten/AI/cts-work && php artisan test && npx playwright test
Execution Strategy
Parallel Execution Waves
Wave 1 (Start Immediately - 4 independent tasks):
|-- Task 1: Fix SlideUploader file wrapper access [quick]
|-- Task 2: Wire SermonBlock + add refreshPage [quick]
|-- Task 3: Sync error message propagation [unspecified-low]
+-- Task 4: Archived services toggle [unspecified-low]
Wave 2 (After Wave 1 - 2 tasks):
|-- Task 5: Upload area side-by-side layout (depends: 1) [visual-engineering]
+-- Task 6: API request log system (depends: 3) [deep]
Wave FINAL (After ALL tasks - 4 parallel reviews):
|-- 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)
Critical Path: Task 1 -> Task 5 ; Task 3 -> Task 6
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 4 (Wave 1)
Dependency Matrix
| Task | Depends On | Blocks |
|---|---|---|
| 1 (Upload fix) | - | 5 (Layout) |
| 2 (Sermon + refreshPage) | - | - |
| 3 (Sync error) | - | 6 (API log) |
| 4 (Archived toggle) | - | - |
| 5 (Layout) | 1 | - |
| 6 (API log) | 3 | - |
| F1-F4 | 1-6 | - |
Agent Dispatch Summary
- Wave 1: 4 tasks - T1: quick, T2: quick, T3: unspecified-low, T4: unspecified-low
- Wave 2: 2 tasks - T5: visual-engineering, T6: deep
- FINAL: 4 tasks - F1: oracle, F2: unspecified-high, F3: unspecified-high, F4: deep
TODOs
-
1. Fix SlideUploader Vue3Dropzone file wrapper access (Bugs 2+3)
What to do:
- In
/Users/thorsten/AI/cts-work/resources/js/Components/SlideUploader.vue, the@jaxtheprime/vue3-dropzonev-model provides wrapper objects{file: File, id: number}, NOT raw File objects - Fix the
uploadNextFile()function — three changes needed:- Line 78: Change
file.nametofile.file.name(extension extraction) - Line 80: Change
file.nametofile.file.name(error message) - Line 86: Change
formData.append('file', file)toformData.append('file', file.file)(actual file upload)
- Line 78: Change
- Add a defensive guard at line 75 area: check if
file.fileexists, and if not, tryfiledirectly (for forward-compatibility if the library changes) - Run
npm run buildto verify no build errors
Must NOT do:
- MUST NOT change SlideController.php backend - it is correct
- MUST NOT modify Vue3Dropzone library code in node_modules
- MUST NOT change the dropzone template/styling
- MUST NOT change the upload progress/error handling logic
Recommended Agent Profile:
- Category:
quick- Reason: Three-line fix in a single file, clear root cause
- Skills: [
playwright]playwright: Needed for QA — verify upload works in browser
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 2, 3, 4)
- Blocks: Task 5 (layout change needs working uploads first)
- Blocked By: None (can start immediately)
References:
Pattern References:
resources/js/Components/SlideUploader.vue:47-117- TheprocessFiles()anduploadNextFile()functions that need fixingresources/js/Components/SlideUploader.vue:78- EXACT line withfile.name.split('.')crashresources/js/Components/SlideUploader.vue:86- EXACT line withformData.append('file', file)— must pass raw File, not wrapper
API/Type References:
- Vue3Dropzone v-model wraps files as
{file: File, id: number}— confirmed fromnode_modules/@jaxtheprime/vue3-dropzone/dist/Vue3Dropzone.es.jslines 113-116
Test References:
tests/e2e/slide-upload.spec.ts- Existing E2E test for slide uploads (may need updating if it tests the actual upload flow)
WHY Each Reference Matters:
- Line 78 is the exact crash site — the TypeError the user reported
- Line 86 is why the upload fails silently even without the crash — sending wrapper object instead of File
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Happy path - Upload a JPG image in Information block Tool: Playwright Preconditions: Logged in, navigate to a service edit page, expand Information block Steps: 1. Navigate to http://cts-work.test/services/{id}/edit (use first available service) 2. Click on the Information block header to ensure it is expanded 3. Find the dropzone element with data-testid="information-block-uploader" 4. Upload a test JPG file (create a small test image or use an existing one from storage) 5. Wait for the upload progress bar to appear and complete 6. Assert no JavaScript errors in console (specifically no "TypeError: can't access property split") 7. Assert the slide grid shows the newly uploaded slide thumbnail Expected Result: Upload succeeds, progress bar shows and completes, new slide appears in grid Failure Indicators: JS TypeError in console, progress stuck at 0%, no new slide in grid Evidence: .sisyphus/evidence/task-1-upload-jpg-happy.png Scenario: Error case - Upload an unsupported file type Tool: Playwright Preconditions: Same as above Steps: 1. Navigate to service edit, expand Information block 2. Try to upload a .txt file 3. Assert the error message appears with data-testid="slide-uploader-error-dismiss" 4. Assert no JS TypeError in console Expected Result: Clean error message "Dateityp nicht erlaubt" without JS crash Evidence: .sisyphus/evidence/task-1-upload-invalid-error.pngCommit: YES
- Message:
fix: resolve Vue3Dropzone file wrapper access in SlideUploader - Files:
resources/js/Components/SlideUploader.vue - Pre-commit:
cd /Users/thorsten/AI/cts-work && npm run build && php artisan test
- In
-
2. Wire SermonBlock in Edit.vue and add missing refreshPage function (Bug 4 + discovered bug)
What to do:
- In
/Users/thorsten/AI/cts-work/resources/js/Pages/Services/Edit.vue:- Add import on line 7 (after ModerationBlock import):
import SermonBlock from '@/Components/Blocks/SermonBlock.vue' - Add
v-else-if="block.key === 'sermon'"block between ModerationBlock (line 262) and SongsBlock (line 264), rendering:<SermonBlock :service-id="service.id" :slides="sermonSlides" @slides-updated="refreshPage" /> - Add the missing
refreshPagefunction in the script section (aftergoBackfunction around line 60):function refreshPage() { router.reload({ preserveScroll: true }) }
- Add import on line 7 (after ModerationBlock import):
- DO NOT create a new SermonBlock.vue — it already exists at
resources/js/Components/Blocks/SermonBlock.vue - The existing SermonBlock.vue accepts
serviceId(Number),slides(Array), and emitsslides-updated - Run
npm run buildto verify no build errors
Must NOT do:
- MUST NOT create a new SermonBlock.vue file — it already exists
- MUST NOT modify SermonBlock.vue itself
- MUST NOT change InformationBlock or ModerationBlock
- MUST NOT change the block accordion/collapsible behavior
Recommended Agent Profile:
- Category:
quick- Reason: Add import, add template block, add one function — all in a single file
- Skills: [
playwright]playwright: Verify sermon block renders correctly in browser
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 3, 4)
- Blocks: None directly
- Blocked By: None (can start immediately)
References:
Pattern References:
resources/js/Pages/Services/Edit.vue:5-7- Existing block imports (InformationBlock, ModerationBlock, SongsBlock) — follow this patternresources/js/Pages/Services/Edit.vue:249-268- Existing v-if/v-else-if chain for block rendering — add sermon case hereresources/js/Pages/Services/Edit.vue:254-@slides-updated="refreshPage"— this event handler needs the function to exist
API/Type References:
resources/js/Components/Blocks/SermonBlock.vue- ALREADY EXISTS. Props:serviceId: Number (required),slides: Array (default []). Emits:slides-updatedresources/js/Pages/Services/Edit.vue:26-29-sermonSlidesprop already defined and passed from backend
Test References:
tests/e2e/service-edit.spec.ts- Existing E2E tests for service edit page
WHY Each Reference Matters:
- Lines 5-7 show the import pattern to follow (exact same style)
- Lines 249-268 show where to insert the new v-else-if (between moderation and songs)
- SermonBlock.vue already exists and is fully implemented — just needs wiring
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Happy path - Sermon block renders with uploader Tool: Playwright Preconditions: Logged in, service exists with agenda Steps: 1. Navigate to http://cts-work.test/services/{id}/edit 2. Find the Predigt block header (contains text "Predigt") 3. Click to expand if collapsed 4. Assert the block content does NOT contain text "Platzhalter" 5. Assert data-testid="sermon-block" exists (or the sermon uploader is visible) 6. Assert a dropzone element is visible within the sermon block Expected Result: Sermon block shows SlideUploader + SlideGrid, not placeholder Failure Indicators: Text "Platzhalter — Komponente folgt" visible Evidence: .sisyphus/evidence/task-2-sermon-block-rendered.png Scenario: refreshPage works after slide upload Tool: Playwright Preconditions: Task 1 must be completed first (upload fix), logged in Steps: 1. Navigate to service edit page 2. Expand Information block 3. Upload a JPG slide 4. After upload completes, verify the page reloads with new slide visible 5. Check there are no JS errors about "refreshPage is not defined" Expected Result: Page reloads preserving scroll, new slide visible in grid Evidence: .sisyphus/evidence/task-2-refresh-page-works.pngCommit: YES
- Message:
fix: wire SermonBlock in Edit.vue and add missing refreshPage function - Files:
resources/js/Pages/Services/Edit.vue - Pre-commit:
cd /Users/thorsten/AI/cts-work && npm run build && php artisan test
- In
-
3. Improve sync error message propagation (Bug 1)
What to do:
- FIRST DIAGNOSTIC STEP: Run
cd /Users/thorsten/AI/cts-work && php artisan cts:syncin terminal to see the ACTUAL error message. Record what error occurs. - In
/Users/thorsten/AI/cts-work/app/Http/Controllers/SyncController.php:- Replace the Artisan::call approach with a direct call to ChurchToolsService::sync()
- Wrap in try/catch to capture the actual exception message
- Return
back()->with('error', 'Sync fehlgeschlagen: ' . $e->getMessage())for failures - Keep the success path returning
back()->with('success', 'Daten wurden aktualisiert')
- If the actual sync error is a configuration issue (wrong URL/token), document what needs to be fixed in .env but do NOT hardcode credentials
- Add a Pest test for the SyncController that verifies error messages are propagated
Must NOT do:
- MUST NOT change ChurchToolsService sync business logic
- MUST NOT add retry logic or complex error recovery
- MUST NOT hardcode API credentials
- MUST NOT change the sync command (SyncChurchToolsCommand.php)
Recommended Agent Profile:
- Category:
unspecified-low- Reason: Simple controller refactor + diagnostic step
- Skills: []
- No special skills needed — backend PHP only
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 2, 4)
- Blocks: Task 6 (API log needs error propagation in place)
- Blocked By: None (can start immediately)
References:
Pattern References:
app/Http/Controllers/SyncController.php:10-19- Current sync() method that only checks exit codeapp/Services/ChurchToolsService.php:28-60- The sync() method that throws exceptions with actual error messagesapp/Console/Commands/SyncChurchToolsCommand.php:15-32- The artisan command that catches and prints errors
API/Type References:
app/Services/ChurchToolsService.php:28-sync()returnsarrayon success, throwsThrowableon failure- The
cts_sync_logtable already logs errors — can be used for additional context
Test References:
tests/Feature/SyncControllerTest.php- Existing tests for sync controller (if exists, check)tests/Feature/ChurchToolsServiceSyncTest.php- Existing sync tests with mocked API
WHY Each Reference Matters:
- SyncController.php is the file to modify — currently swallows errors via Artisan exit code
- ChurchToolsService.php shows the actual exception that gets thrown — we need to catch it in the controller
- The artisan command is NOT modified but understanding its flow helps
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Happy path - Sync succeeds Tool: Bash (curl) Preconditions: App running, valid CTS API credentials in .env Steps: 1. Run: cd /Users/thorsten/AI/cts-work && php artisan test --filter=Sync 2. All sync-related tests should pass Expected Result: Tests pass, no regressions Evidence: .sisyphus/evidence/task-3-sync-tests-pass.txt Scenario: Error case - Sync fails with descriptive message Tool: Playwright Preconditions: Logged in at http://cts-work.test Steps: 1. Navigate to dashboard or services page 2. Click the sync button in the top navigation bar 3. If sync fails, check the flash message contains specific error details (not just "Fehler beim Synchronisieren") 4. If sync succeeds, verify success message appears Expected Result: Either success message or specific error message (e.g., "Connection refused", "Unauthorized", etc.) Failure Indicators: Generic "Fehler beim Synchronisieren" without details Evidence: .sisyphus/evidence/task-3-sync-error-message.pngCommit: YES
- Message:
fix: propagate actual sync error messages to frontend - Files:
app/Http/Controllers/SyncController.php - Pre-commit:
cd /Users/thorsten/AI/cts-work && php artisan test
- FIRST DIAGNOSTIC STEP: Run
-
4. Add archived services toggle to services list (Feature 6)
What to do:
- BACKEND: In
/Users/thorsten/AI/cts-work/app/Http/Controllers/ServiceController.phpindex()method:- Accept query parameter:
$archived = request()->boolean('archived') - If archived: change query to
whereDate('date', '<', Carbon::today())->orderByDesc('date') - If not archived (default): keep existing
whereDate('date', '>=', Carbon::today())->orderBy('date') - Pass
archivedflag to the frontend:'archived' => $archived
- Accept query parameter:
- FRONTEND: In
/Users/thorsten/AI/cts-work/resources/js/Pages/Services/Index.vue:- Add a new prop:
archived: { type: Boolean, default: false } - Add a ref:
const showArchived = ref(props.archived) - Add toggle switch in the header area (between h2 and p elements, or after the description)
- Toggle switch styling: use a simple pill-style toggle with labels "Kommende" / "Vergangene"
- On toggle change:
router.get(route('services.index'), { archived: !showArchived.value }, { preserveState: true, preserveScroll: true }) - Update empty state text based on mode: "Keine kommenden Services vorhanden." / "Keine vergangenen Services vorhanden."
- Update header description based on mode
- Add a new prop:
- All text in German (Du, not Sie)
- Add a Pest test for the archived filter in ServiceController
Must NOT do:
- MUST NOT add date range filters, search, or pagination to archived view
- MUST NOT change the service data structure or model
- MUST NOT add complex sorting options — just binary toggle
- MUST NOT change finalize/reopen/download functionality
Recommended Agent Profile:
- Category:
unspecified-low- Reason: Backend query param + frontend toggle — straightforward full-stack change
- Skills: [
frontend-ui-ux]frontend-ui-ux: Nice toggle switch design
Parallelization:
- Can Run In Parallel: YES
- Parallel Group: Wave 1 (with Tasks 1, 2, 3)
- Blocks: None
- Blocked By: None (can start immediately)
References:
Pattern References:
app/Http/Controllers/ServiceController.php:16-63- Current index() method with hardcoded future-only filterapp/Http/Controllers/ServiceController.php:19- The specific linewhereDate('date', '>=', Carbon::today())to make conditionalresources/js/Pages/Services/Index.vue:6-11- Props definition — addarchivedprop hereresources/js/Pages/Services/Index.vue:170-179- Header template — add toggle here
API/Type References:
- Inertia router:
router.get(url, data, options)for navigation with query params resources/js/Pages/Services/Index.vue:201-203- Existing empty state text to make conditional
Test References:
tests/Feature/ServiceControllerTest.php- Existing tests for ServiceController
WHY Each Reference Matters:
- ServiceController line 19 is the exact filter to make conditional
- Index.vue lines 170-179 is where the toggle UI goes
- Existing tests verify current behavior — new test verifies archived behavior
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Happy path - Toggle shows archived services Tool: Playwright Preconditions: Logged in, some services exist with past dates Steps: 1. Navigate to http://cts-work.test/services 2. Assert the toggle exists (find by text "Kommende" or "Vergangene") 3. Default state should show future/today services 4. Click the toggle to switch to archived 5. Assert URL changes to include ?archived=1 6. Assert the service list shows past services (dates before today) 7. Assert services are ordered newest-first (most recent past first) Expected Result: Toggle switches between future and past services, URL updates Failure Indicators: Toggle not visible, page doesn't change, URL doesn't update Evidence: .sisyphus/evidence/task-4-archived-toggle.png Scenario: Empty state for archived view Tool: Playwright Preconditions: Logged in, no past services in DB (or clean test state) Steps: 1. Navigate to http://cts-work.test/services?archived=1 2. Assert empty state message contains "Keine vergangenen Services" Expected Result: Appropriate German empty state message Evidence: .sisyphus/evidence/task-4-archived-empty-state.pngCommit: YES
- Message:
feat: add archived services toggle to services list - Files:
app/Http/Controllers/ServiceController.php,resources/js/Pages/Services/Index.vue - Pre-commit:
cd /Users/thorsten/AI/cts-work && php artisan test && npm run build
- BACKEND: In
-
5. Reposition upload area to the right of slides grid (Feature 5)
What to do:
- In
InformationBlock.vue,ModerationBlock.vue, andSermonBlock.vue:- Change the stacked layout (SlideUploader above SlideGrid) to side-by-side
- Wrap SlideGrid + SlideUploader in a flex container:
<div class="flex flex-col lg:flex-row-reverse gap-6"> - SlideUploader goes in
<div class="lg:w-1/3">(right side, smaller) - SlideGrid goes in
<div class="flex-1 lg:w-2/3">(left side, larger) - Use
flex-row-reverseso the uploader is on the RIGHT in the HTML flow but appears right visually - Or simply order: grid first, uploader second in the HTML with standard flex-row
- In
SlideUploader.vueCSS: Reduce dropzonemin-heightfrom 160px to 120px in the.slide-dropzone :deep(.v3-dropzone)rule - Reduce the dropzone padding from
2rem 1.5remto1.5rem 1rem - Ensure responsive: On mobile (<1024px / below
lgbreakpoint), stack vertically with uploader on top - Keep ALL three blocks consistent — same layout pattern in all three
- Run
npm run buildto verify
Must NOT do:
- MUST NOT change SlideUploader.vue functionality — only CSS/layout wrapping
- MUST NOT change SlideGrid.vue functionality or styling
- MUST NOT break mobile responsiveness
- MUST NOT change block header/accordion behavior
- MUST NOT change the upload progress bar or error message positioning
Recommended Agent Profile:
- Category:
visual-engineering- Reason: CSS layout change across multiple components, responsive design
- Skills: [
frontend-ui-ux,playwright]frontend-ui-ux: Proper responsive layout designplaywright: Visual verification of layout at different viewports
Parallelization:
- Can Run In Parallel: YES (with Task 6)
- Parallel Group: Wave 2 (with Task 6)
- Blocks: None
- Blocked By: Task 1 (uploads must work before testing layout)
References:
Pattern References:
resources/js/Components/Blocks/InformationBlock.vue:104-121- Current stacked layout (SlideUploader then SlideGrid)resources/js/Components/Blocks/ModerationBlock.vue:50-68- Same stacked layout patternresources/js/Components/Blocks/SermonBlock.vue- Same pattern (check exact lines)resources/js/Components/SlideUploader.vue:254-262- CSS for.slide-dropzone :deep(.v3-dropzone)withmin-height: 160px
WHY Each Reference Matters:
- All three blocks need the same layout change for consistency
- SlideUploader CSS controls the dropzone size — needs shrinking for the narrower right column
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Desktop layout - uploader right of grid Tool: Playwright Preconditions: Logged in, service edit page Steps: 1. Set viewport to 1280x800 (desktop) 2. Navigate to http://cts-work.test/services/{id}/edit 3. Expand Information block 4. Take screenshot 5. Assert the dropzone and grid are side-by-side (dropzone is to the right) 6. Verify dropzone appears smaller than the grid area Expected Result: Side-by-side layout with grid on left (~70%), uploader on right (~30%) Evidence: .sisyphus/evidence/task-5-desktop-layout.png Scenario: Mobile layout - stacked vertically Tool: Playwright Preconditions: Same service edit page Steps: 1. Set viewport to 375x812 (mobile) 2. Navigate to same service edit page 3. Expand Information block 4. Take screenshot 5. Assert uploader and grid are stacked vertically (not side-by-side) Expected Result: Stacked vertical layout on mobile Evidence: .sisyphus/evidence/task-5-mobile-layout.png Scenario: All three blocks have consistent layout Tool: Playwright Preconditions: Desktop viewport, service edit page Steps: 1. Expand all blocks (Information, Moderation, Predigt) 2. Take screenshots of each 3. Assert all three have the same side-by-side layout pattern Expected Result: Consistent layout across all slide-based blocks Evidence: .sisyphus/evidence/task-5-consistent-blocks.pngCommit: YES
- Message:
feat: reposition upload area to the right of slides grid - Files:
resources/js/Components/Blocks/InformationBlock.vue,resources/js/Components/Blocks/ModerationBlock.vue,resources/js/Components/Blocks/SermonBlock.vue,resources/js/Components/SlideUploader.vue - Pre-commit:
cd /Users/thorsten/AI/cts-work && npm run build && php artisan test
- In
-
6. Add CTS API request logging with searchable frontend UI (Feature 7)
What to do:
A) Database Migration:
- Create migration:
database/migrations/2026_03_02_100000_create_api_request_logs_table.php - Columns:
id,method(string, e.g. 'fetchEvents', 'fetchSongs', 'syncAgenda', 'getEventServices'),endpoint(string, the logical operation),status(string: 'success'/'error'),request_context(json, nullable — e.g. event_id, parameters),response_summary(text, nullable — e.g. "Found 5 events"),error_message(text, nullable),duration_ms(integer),sync_log_id(nullable FK to cts_sync_log.id),created_at,updated_at - Add index on
statusandcreated_at
B) Model:
- Create
app/Models/ApiRequestLog.phpwith fillable fields, casts for json columns - Add scope for filtering by status
- Add scope for search (method, endpoint, error_message)
C) Service Logging Wrapper:
- In
app/Services/ChurchToolsService.php, wrap the four main API methods with logging:fetchEvents()— log before/after with timingfetchSongs()— log before/after with timingsyncAgenda($eventId)— log before/after with timing and event_id contextgetEventServices($eventId)— log before/after with timing and event_id context
- Create a private helper method
logApiCall(string $method, string $endpoint, Closure $operation, ?array $context = null): mixedthat:- Records start time
- Executes the operation in try/catch
- On success: logs with status 'success', duration, response summary
- On error: logs with status 'error', duration, error_message from exception
- Re-throws the exception after logging
- The sync() method already writes to cts_sync_log — link the api_request_logs to the current sync_log_id if available
D) Controller:
- Create
app/Http/Controllers/ApiLogController.phpwithindex()method - Accept query params:
search(string),status(string: 'success'/'error'/null for all),page(int) - Query ApiRequestLog with filters, paginate (25 per page), order by created_at DESC
- Return Inertia page:
ApiLogs/Index
E) Route:
- In
routes/web.php, inside the auth middleware group, add:Route::get('/api-logs', [ApiLogController::class, 'index'])->name('api-logs.index')
F) Vue Page:
- Create
resources/js/Pages/ApiLogs/Index.vue - Table with columns: Zeitpunkt, Methode, Endpunkt, Status, Dauer (ms), Fehler
- Status column: green badge for success, red badge for error
- Error rows highlighted with light red background
- Search input field (debounced, searches method/endpoint/error_message)
- Status filter dropdown: Alle / Erfolg / Fehler
- Pagination using Inertia pagination (Laravel paginator)
- All text in German (Du, not Sie)
G) Navigation:
- In
resources/js/Layouts/AuthenticatedLayout.vue, add a NavLink for "API-Log" after the Song-Datenbank link - Use
route('api-logs.index')withroute().current('api-logs.*')for active state
H) Pest Tests:
- Test ApiLogController index returns correct page
- Test search filter works
- Test status filter works
- Test ApiRequestLog model scopes
Must NOT do:
- MUST NOT restructure ChurchToolsService sync logic — only add logging wrapper
- MUST NOT add charts, dashboards, or export functionality
- MUST NOT add email alerts for errors
- MUST NOT add auto-refresh or WebSocket updates
- MUST NOT add retention/cleanup (note for later)
Recommended Agent Profile:
- Category:
deep- Reason: Full-stack feature: migration, model, service wrapper, controller, routes, Vue page, nav link, tests
- Skills: [
frontend-ui-ux,playwright]frontend-ui-ux: Table design, badges, search UIplaywright: QA verification of the log page
Parallelization:
- Can Run In Parallel: YES (with Task 5)
- Parallel Group: Wave 2 (with Task 5)
- Blocks: None
- Blocked By: Task 3 (sync error propagation should be in place first)
References:
Pattern References:
app/Http/Controllers/ServiceController.php:16-63- Controller pattern with Inertia::render, query building, mappingapp/Services/ChurchToolsService.php:143-163-fetchEvents()andfetchSongs()methods to wrap with loggingapp/Services/ChurchToolsService.php:119-128-syncAgenda()method to wrapapp/Services/ChurchToolsService.php:130-141-getEventServices()method to wrapapp/Services/ChurchToolsService.php:342-353-writeSyncLog()method — similar pattern for writing API logsresources/js/Pages/Services/Index.vue- Table/list page pattern with Inertia propsresources/js/Layouts/AuthenticatedLayout.vue:96-109- NavLink pattern for adding new nav item
API/Type References:
database/migrations/- Check existing migration naming convention for timestamp prefixapp/Models/Slide.phporapp/Models/Service.php- Model pattern with fillable, casts
Test References:
tests/Feature/ServiceControllerTest.php- Controller test patterntests/Feature/ChurchToolsServiceSyncTest.php- Service test pattern with mocked dependencies
WHY Each Reference Matters:
- ServiceController shows the Inertia controller pattern to follow exactly
- ChurchToolsService methods (fetchEvents, fetchSongs, etc.) are the exact methods to wrap
- writeSyncLog shows how the service already writes to DB — follow same pattern for API logs
- Services/Index.vue shows the table page pattern
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: Happy path - API log page loads with entries Tool: Playwright Preconditions: Logged in, trigger a sync first to generate log entries Steps: 1. Navigate to http://cts-work.test/api-logs 2. Assert the page loads with a table 3. Assert table has columns: Zeitpunkt, Methode, Status, Dauer 4. If sync was triggered, assert at least one row exists 5. Check that error rows (if any) have red/highlighted styling Expected Result: Table loads with log entries, errors highlighted in red Evidence: .sisyphus/evidence/task-6-api-log-page.png Scenario: Search and filter work Tool: Playwright Preconditions: Multiple log entries exist (trigger sync multiple times) Steps: 1. Navigate to http://cts-work.test/api-logs 2. Type "fetchEvents" in the search input 3. Assert only rows with "fetchEvents" method are shown 4. Clear search, select "Fehler" from status filter 5. Assert only error rows are shown (or empty if no errors) Expected Result: Search and filter narrow results correctly Evidence: .sisyphus/evidence/task-6-api-log-filter.png Scenario: Navigation link exists Tool: Playwright Preconditions: Logged in Steps: 1. Navigate to http://cts-work.test/dashboard 2. Find "API-Log" link in the top navigation bar 3. Click it 4. Assert URL is http://cts-work.test/api-logs Expected Result: API-Log nav link exists and navigates correctly Evidence: .sisyphus/evidence/task-6-api-log-nav.png Scenario: Migration runs without errors Tool: Bash Preconditions: Fresh migration state Steps: 1. Run: cd /Users/thorsten/AI/cts-work && php artisan migrate 2. Assert exit code 0 3. Run: php artisan test 4. Assert all tests pass Expected Result: Migration succeeds, all tests pass Evidence: .sisyphus/evidence/task-6-migration-tests.txtCommit: YES
- Message:
feat: add CTS API request logging with searchable frontend UI - Files:
database/migrations/2026_03_02_100000_create_api_request_logs_table.php,app/Models/ApiRequestLog.php,app/Http/Controllers/ApiLogController.php,app/Services/ChurchToolsService.php,routes/web.php,resources/js/Pages/ApiLogs/Index.vue,resources/js/Layouts/AuthenticatedLayout.vue - Pre-commit:
cd /Users/thorsten/AI/cts-work && php artisan test && npm run build
- Create migration:
Final Verification Wave
4 review agents run in PARALLEL. ALL must APPROVE. Rejection -> fix -> re-run.
-
F1. Plan Compliance Audit - oracle 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 Review - unspecified-high Run vue-tsc --noEmit + php artisan test + npx playwright test. Review all changed files for: as any, empty catches, console.log in prod, commented-out code, unused imports. Output: Build [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT
-
F3. Real Manual QA - unspecified-high (+ playwright skill) Start from clean state at http://cts-work.test. Execute EVERY QA scenario from EVERY task. Test cross-task integration. Save to .sisyphus/evidence/final-qa/. Output: Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT
-
F4. Scope Fidelity Check - deep For each task: read What to do, read actual diff. Verify 1:1. Check Must NOT do compliance. Detect cross-task contamination. Output: Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT
Commit Strategy
- Task 1:
fix: resolve Vue3Dropzone file wrapper access in SlideUploader- resources/js/Components/SlideUploader.vue - Task 2:
fix: wire SermonBlock in Edit.vue and add missing refreshPage function- resources/js/Pages/Services/Edit.vue - Task 3:
fix: propagate actual sync error messages to frontend- app/Http/Controllers/SyncController.php - Task 4:
feat: add archived services toggle to services list- ServiceController.php, Services/Index.vue - Task 5:
feat: reposition upload area to the right of slides grid- Block components - Task 6:
feat: add CTS API request logging with searchable frontend UI- migration, model, controller, routes, Vue page
Success Criteria
Verification Commands
cd /Users/thorsten/AI/cts-work
php artisan test # Expected: 174+ tests, 0 failures
npx playwright test # Expected: 83+ tests, 0 failures
npm run build # Expected: exit 0, no errors
php artisan migrate # Expected: no pending migrations
Final Checklist
- All Must Have items present and verified
- All Must NOT Have items absent (checked via search)
- All 174+ Pest tests pass
- All 83+ Playwright tests pass
- Build succeeds
- All text in German (Du, not Sie)