pp-planer/.sisyphus/plans/cts-bugfix-features.md
Thorsten Bus 2cb7c6ab13 docs: mark all tasks complete in cts-bugfix-features plan
- 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
2026-03-02 11:19:22 +01:00

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 build completes 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-dropzone v-model provides wrapper objects {file: File, id: number}, NOT raw File objects
    • Fix the uploadNextFile() function — three changes needed:
      • Line 78: Change file.name to file.file.name (extension extraction)
      • Line 80: Change file.name to file.file.name (error message)
      • Line 86: Change formData.append('file', file) to formData.append('file', file.file) (actual file upload)
    • Add a defensive guard at line 75 area: check if file.file exists, and if not, try file directly (for forward-compatibility if the library changes)
    • Run npm run build to 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 - The processFiles() and uploadNextFile() functions that need fixing
    • resources/js/Components/SlideUploader.vue:78 - EXACT line with file.name.split('.') crash
    • resources/js/Components/SlideUploader.vue:86 - EXACT line with formData.append('file', file) — must pass raw File, not wrapper

    API/Type References:

    • Vue3Dropzone v-model wraps files as {file: File, id: number} — confirmed from node_modules/@jaxtheprime/vue3-dropzone/dist/Vue3Dropzone.es.js lines 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.png
    

    Commit: 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
  • 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 refreshPage function in the script section (after goBack function around line 60): function refreshPage() { router.reload({ preserveScroll: true }) }
    • 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 emits slides-updated
    • Run npm run build to 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 pattern
    • resources/js/Pages/Services/Edit.vue:249-268 - Existing v-if/v-else-if chain for block rendering — add sermon case here
    • resources/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-updated
    • resources/js/Pages/Services/Edit.vue:26-29 - sermonSlides prop 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.png
    

    Commit: 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
  • 3. Improve sync error message propagation (Bug 1)

    What to do:

    • FIRST DIAGNOSTIC STEP: Run cd /Users/thorsten/AI/cts-work && php artisan cts:sync in 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 code
    • app/Services/ChurchToolsService.php:28-60 - The sync() method that throws exceptions with actual error messages
    • app/Console/Commands/SyncChurchToolsCommand.php:15-32 - The artisan command that catches and prints errors

    API/Type References:

    • app/Services/ChurchToolsService.php:28 - sync() returns array on success, throws Throwable on failure
    • The cts_sync_log table 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.png
    

    Commit: 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
  • 4. Add archived services toggle to services list (Feature 6)

    What to do:

    • BACKEND: In /Users/thorsten/AI/cts-work/app/Http/Controllers/ServiceController.php index() 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 archived flag to the frontend: 'archived' => $archived
    • 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
    • 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 filter
    • app/Http/Controllers/ServiceController.php:19 - The specific line whereDate('date', '>=', Carbon::today()) to make conditional
    • resources/js/Pages/Services/Index.vue:6-11 - Props definition — add archived prop here
    • resources/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.png
    

    Commit: 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
  • 5. Reposition upload area to the right of slides grid (Feature 5)

    What to do:

    • In InformationBlock.vue, ModerationBlock.vue, and SermonBlock.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-reverse so 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.vue CSS: Reduce dropzone min-height from 160px to 120px in the .slide-dropzone :deep(.v3-dropzone) rule
    • Reduce the dropzone padding from 2rem 1.5rem to 1.5rem 1rem
    • Ensure responsive: On mobile (<1024px / below lg breakpoint), stack vertically with uploader on top
    • Keep ALL three blocks consistent — same layout pattern in all three
    • Run npm run build to 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 design
      • playwright: 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 pattern
    • resources/js/Components/Blocks/SermonBlock.vue - Same pattern (check exact lines)
    • resources/js/Components/SlideUploader.vue:254-262 - CSS for .slide-dropzone :deep(.v3-dropzone) with min-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.png
    

    Commit: 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
  • 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 status and created_at

    B) Model:

    • Create app/Models/ApiRequestLog.php with 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 timing
      • fetchSongs() — log before/after with timing
      • syncAgenda($eventId) — log before/after with timing and event_id context
      • getEventServices($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): mixed that:
      • 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.php with index() 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') with route().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 UI
      • playwright: 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, mapping
    • app/Services/ChurchToolsService.php:143-163 - fetchEvents() and fetchSongs() methods to wrap with logging
    • app/Services/ChurchToolsService.php:119-128 - syncAgenda() method to wrap
    • app/Services/ChurchToolsService.php:130-141 - getEventServices() method to wrap
    • app/Services/ChurchToolsService.php:342-353 - writeSyncLog() method — similar pattern for writing API logs
    • resources/js/Pages/Services/Index.vue - Table/list page pattern with Inertia props
    • resources/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 prefix
    • app/Models/Slide.php or app/Models/Service.php - Model pattern with fillable, casts

    Test References:

    • tests/Feature/ServiceControllerTest.php - Controller test pattern
    • tests/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.txt
    

    Commit: 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

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)