# 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
- [x] All 174 Pest tests pass: `cd /Users/thorsten/AI/cts-work && php artisan test`
- [x] All 83 E2E Playwright tests pass: `cd /Users/thorsten/AI/cts-work && npx playwright test`
- [x] `npm run build` completes without errors
- [x] 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

- [x] 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`

- [x] 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`

- [x] 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`

- [x] 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`

- [x] 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`

- [x] 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.

- [x] 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

- [x] 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

- [x] 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

- [x] 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
```bash
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
- [x] All Must Have items present and verified
- [x] All Must NOT Have items absent (checked via search)
- [x] All 174+ Pest tests pass
- [x] All 83+ Playwright tests pass
- [x] Build succeeds
- [x] All text in German (Du, not Sie)
