This commit is contained in:
2026-05-06 00:18:37 +02:00
parent 09e0ce8dc0
commit ea77c8ea35
25 changed files with 1996 additions and 34 deletions

99
.paul/PROJECT.md Normal file
View File

@@ -0,0 +1,99 @@
# jachty3.pagedev.pl
## What This Is
Strona internetowa wypożyczalni jachtów oparta na WordPress z customowym pluginem `yacht-booking-system`. System umożliwia prezentację floty jachtów, rezerwację przez frontend (kalendarz FullCalendar), zarządzanie rezerwacjami przez panel admina oraz dwukierunkową synchronizację z Google Calendar.
## Core Value
Klienci mogą sprawdzić dostępność jachtu i złożyć rezerwację przez stronę bez kontaktu z właścicielem.
## Current State
| Attribute | Value |
|-----------|-------|
| Version | 1.0.0 |
| Status | Beta |
| Last Updated | 2026-05-05 |
**Plugin:** `wp-content/plugins/yacht-booking-system/`
## Requirements
### Validated (Shipped)
- [x] Custom Post Types: `yacht`, `yacht_booking` — faza 1
- [x] Admin CRUD dla jachtów — faza 2
- [x] System dostępności z cache (`wp_yacht_availability`) — faza 3
- [x] Frontend kalendarz FullCalendar.js + Elementor widget + shortcode — faza 4
- [x] Backend formularza rezerwacji (REST API + email do admina) — faza 5
- [x] Admin zarządzanie rezerwacjami (approve/cancel/delete + email do klienta) — faza 6
- [x] Google Calendar OAuth 2.0 — faza 7
- [x] Synchronizacja dwukierunkowa Google Calendar ↔ WordPress — faza 8
- [x] Szablony email (WYSIWYG + tagi) — aktualizacja Codex 2026-02-11
- [x] Eksport CSV rezerwacji — aktualizacja Codex 2026-02-11
### Active (In Progress)
- [ ] Faza 9: Finalizacja (settings, testy, tłumaczenia, dokumentacja)
### Planned (Next)
- [ ] Tłumaczenia PL (.pot/.po/.mo)
- [ ] Security audit (nonce, escaping, SQL, capabilities)
- [ ] Testy cross-browser i mobile
- [ ] Dokumentacja użytkownika i dewelopera
### Out of Scope
- Integracja z systemami płatności (Stripe/PayPal) — poza obecnym zakresem
## Target Users
**Primary:** Właściciel wypożyczalni jachtów (admin)
- Zarządza flotą i rezerwacjami przez WP Admin
- Chce synchronizację z Google Calendar
**Secondary:** Klienci wypożyczalni
- Sprawdzają dostępność i składają rezerwacje przez frontend
## Context
**Technical Context:**
- WordPress + PHP 7.4+
- Elementor Pro (page builder)
- FullCalendar.js v6 (CDN)
- Google Calendar API (OAuth 2.0, natywna implementacja bez SDK)
- MySQL — custom tabela `wp_yacht_availability`
- REST API: namespace `yacht-booking/v1`
## Constraints
### Technical Constraints
- WordPress hosting (FTP deployment via ftp-kr)
- PHP 7.4+ (bez Composer/autoload PSR-4 — ładowanie manualne)
- FullCalendar via CDN (bez npm/bundlera)
## Key Decisions
| Decision | Rationale | Date | Status |
|----------|-----------|------|--------|
| Własna implementacja OAuth bez Google PHP Client | Lżejsze rozwiązanie bez zewnętrznych zależności | 2026-02-11 | Active |
| FullCalendar via CDN | Brak środowiska npm/Webpack na serwerze WP | 2026-02-11 | Active |
| Custom tabela `wp_yacht_availability` zamiast meta | Wydajność zapytań dostępności | 2026-02-11 | Active |
## Tech Stack
| Layer | Technology | Notes |
|-------|------------|-------|
| CMS | WordPress 6.x | |
| PHP | PHP 7.4+ | Singleton pattern, namespace YachtBooking\ |
| Frontend | FullCalendar.js v6 | CDN, jQuery |
| Page Builder | Elementor Pro | Custom widget |
| Database | MySQL | Custom table + CPT meta |
| Calendar Sync | Google Calendar API v3 | OAuth 2.0, native WP HTTP API |
| Deployment | FTP (ftp-kr VS Code extension) | |
---
*PROJECT.md — Updated when requirements or context change*
*Last updated: 2026-05-05*

54
.paul/ROADMAP.md Normal file
View File

@@ -0,0 +1,54 @@
# Roadmap: jachty3.pagedev.pl
## Overview
Plugin `yacht-booking-system` dla WordPress budowany w 9 fazach. Fazy 1-8 ukończone (89%). Pozostała faza 9 — finalizacja, testy i dokumentacja.
## Current Milestone
**v1.0 Production Release** (v1.0.0)
Status: In progress
Phases: 8 of 9 complete
## Phases
| Phase | Name | Plans | Status | Completed |
|-------|------|-------|--------|-----------|
| 1 | Fundament | 1 | ✅ Complete | 2026-02-11 |
| 2 | Admin — zarządzanie jachtami | 1 | ✅ Complete | 2026-02-11 |
| 3 | System dostępności | 1 | ✅ Complete | 2026-02-11 |
| 4 | Frontend — kalendarz | 1 | ✅ Complete | 2026-02-11 |
| 5 | Backend formularza rezerwacji | 1 | ✅ Complete | 2026-02-11 |
| 6 | Admin — zarządzanie rezerwacjami | 1 | ✅ Complete | 2026-02-11 |
| 7 | Google Calendar — autentykacja | 1 | ✅ Complete | 2026-02-11 |
| 8 | Google Calendar — synchronizacja | 1 | ✅ Complete | 2026-02-11 |
| 9 | Finalizacja | TBD | In progress | - |
## Phase Details
### Phase 18: Ukończone
Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`.
### Phase 9: Finalizacja
**Goal:** Plugin gotowy produkcyjnie — ustawienia, bezpieczeństwo, testy, tłumaczenia, dokumentacja
**Depends on:** Phase 8 (synchronizacja GCal)
**Research:** Unlikely (wewnętrzne wzorce WP)
**Scope:**
- Settings Page — uzupełnienie (default status, email from, format daty, waluta, T&C)
- Security audit (nonce, escaping, SQL injection, capabilities)
- Testy funkcjonalne i cross-browser
- Tłumaczenia PL (.pot/.po/.mo)
- Dokumentacja użytkownika i dewelopera
**Plans:**
- [ ] 09-01: UX/UI polish kalendarza — half-day na pierwszym/ostatnim dniu rezerwacji + paleta widgetu pasująca do strony
- [ ] 09-02: Settings Page — uzupełnienie brakujących opcji
- [ ] 09-03: Security audit i poprawki
- [ ] 09-04: Testy + tłumaczenia + dokumentacja
---
*Roadmap created: 2026-05-05*
*Last updated: 2026-05-05*

60
.paul/STATE.md Normal file
View File

@@ -0,0 +1,60 @@
# Project State
## Project Reference
See: .paul/PROJECT.md (updated 2026-05-05)
**Core value:** Klienci mogą sprawdzić dostępność jachtu i złożyć rezerwację przez stronę bez kontaktu z właścicielem.
**Current focus:** v1.0 Production Release — Phase 9: Finalizacja
## Current Position
Milestone: v1.0 Production Release (v1.0.0)
Phase: 9 of 9 (Finalizacja) — In progress
Plan: 09-01 — Complete (UX/UI kalendarza)
Status: Loop closed, ready for next plan (09-02)
Last activity: 2026-05-06 — Closed loop 09-01 (skos 45°, ciemne ramki, paleta brand strony)
Progress:
- Milestone: [████████░░] 82%
- Phase 9: [██░░░░░░░░] 25% (1 of 4 plans complete)
## Loop Position
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
✓ ✓ ✓ [Loop 09-01 complete, ready for 09-02]
```
## Accumulated Context
### Decisions
| Decision | Phase | Impact |
|----------|-------|--------|
| OAuth bez Google PHP Client | 7 | Brak zależności zewnętrznych, manual token refresh |
| FullCalendar via CDN | 4 | Brak bundlera, update ręczny |
| Custom tabela availability | 1 | Wydajne zapytania, custom cache logic |
### Deferred Issues
| Issue | Origin | Effort | Revisit |
|-------|--------|--------|---------|
| Tłumaczenia PL | Phase 1-8 | M | Phase 9 |
| Security audit | Phase 1-8 | M | Phase 9 |
| Dokumentacja PHP Doc | Phase 1-8 | L | Phase 9 |
### Blockers/Concerns
None.
## Session Continuity
Last session: 2026-05-06
Stopped at: Loop 09-01 closed (UX/UI kalendarza ukończone, klient zaakceptował)
Next action: Run /paul:plan to plan 09-02 (Settings Page — uzupełnienie brakujących opcji)
Resume file: .paul/phases/09-finalizacja/09-01-SUMMARY.md
---
*STATE.md — Updated after every significant action*

View File

@@ -0,0 +1,24 @@
# 2026-05-06
## Co zrobiono
- [Phase 9, Plan 01] UX/UI polish kalendarza: skos 45° na pierwszym/ostatnim dniu rezerwacji, ciemne ramki dni, paleta widgetu dopasowana do brandu strony (granat #0e2036, czerwień #bc1834)
- Edge detection po stronie klienta — porównanie sąsiadujących dni po booking_id+status, klasy `yacht-day-{booked|blocked}-{start|end|mid|single}`
- Gradient 135° na obu skrajnych dniach (parallelogram przez wiersz), single-day blokada renderowana jako pełen booked
- CSS variables `--yacht-available-bg` / `--yacht-booked-bg` ustawiane z JS na wrapperze — reuse w gradientach + legendzie
- Nagłówki kolumn FullCalendar w granacie, ramki przez `--fc-border-color: #0e2036` + override 7 selektorów
- Cyfry dni `#021526` (past `#6c757d`) — przebicie globalnej reguły `body a:not([href])`
- Legenda rozszerzona o 3-ci swatch „Dzień odbioru / zwrotu" z gradientem
- Helper `test-add-booking.php` (token-protected, akcje add/list/remove) do smoke testu — DO USUNIĘCIA z FTP po testach
## Zmienione pliki
- `wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js`
- `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss`
- `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css`
- `wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php`
- `test-add-booking.php` (nowy, helper testowy)
- `.paul/phases/09-finalizacja/09-01-PLAN.md` (nowy)
- `.paul/phases/09-finalizacja/09-01-SUMMARY.md` (nowy)
- `.paul/STATE.md`
- `.paul/ROADMAP.md`

View File

@@ -0,0 +1,130 @@
# Architecture
**Analysis Date:** 2026-05-05
## Pattern Overview
WordPress plugin with layered architecture: CPT-backed data model, REST API surface, Singleton orchestrators, static utility classes.
- Entry point bootstraps a single Singleton (`Yacht_Booking`) via `plugins_loaded`
- All public-facing state changes go through REST API (`/wp-json/yacht-booking/v1/`)
- Admin state changes use WordPress PRG pattern (form POST → redirect)
- External calendar sync handled by separate Integration namespaces
## Class Inventory
| Class | File | Pattern | Responsibility |
|-------|------|---------|---------------|
| `Yacht_Booking` | `includes/class-yacht-booking.php` | Singleton | Master orchestrator — wires all subsystems |
| `Yacht` | `includes/class-yacht.php` | Static utility | `yacht` CPT registration + meta accessors |
| `Booking` | `includes/class-booking.php` | Static utility | `yacht_booking` CPT + `create()` + status |
| `Inquiry` | `includes/class-inquiry.php` | Static utility | `yacht_inquiry` CPT + `send_emails()` |
| `Availability` | `includes/class-availability.php` | Static utility | All `wp_yacht_availability` table operations |
| `Rest_Controller` | `api/class-rest-controller.php` | Extends `WP_REST_Controller` | 7 REST endpoints, booking orchestration |
| `Settings` | `includes/class-settings.php` | Static utility | Typed `wp_options` accessors + formatting helpers |
| `Installer` | `includes/class-installer.php` | Plain class | DB table creation + default options on activation |
| `Email_Templates` | `includes/class-email-templates.php` | Static utility | Template storage, compilation, tag replacement |
| `Admin` | `admin/class-admin.php` | Singleton (admin-only) | Menu, form processing, CSV export, customer emails |
| `Booking_List_Table` | `admin/class-booking-list-table.php` | Extends `WP_List_Table` | Bookings admin table |
| `Yacht_List_Table` | `admin/class-yacht-list-table.php` | Extends `WP_List_Table` | Yachts admin table |
| `Inquiry_List_Table` | `admin/class-inquiry-list-table.php` | Extends `WP_List_Table` | Inquiries admin table |
| `Sync_Controller` | `integrations/google-calendar/class-sync-controller.php` | Singleton | GCal cron sync orchestration |
| `GCal_Service` | `integrations/google-calendar/class-gcal-service.php` | Plain class | Google Calendar API calls |
| `OAuth_Handler` | `integrations/google-calendar/class-oauth-handler.php` | Plain class | OAuth 2.0 token storage + refresh |
| `ICal_Feed` | `integrations/ical/class-ical-feed.php` | Static/plain | iCal feed generation + rewrite rule |
| `ICal_Import` | `integrations/ical/class-ical-import.php` | Static/plain | External iCal URL import + cron |
| `Shortcode` | `frontend/class-shortcode.php` | Singleton | `[yacht_calendar]` shortcode |
| `Calendar_Widget` | `frontend/class-calendar-widget.php` | Extends Elementor `Widget_Base` | Elementor `yacht-calendar` widget |
## Dependency Graph
```
yacht-booking-system.php
└── Yacht_Booking (Singleton)
├── Yacht (static)
├── Booking (static)
│ └── fires: yacht_booking_created
│ yacht_booking_status_changed
├── Availability (static)
├── Settings (static)
├── Email_Templates (static)
├── Rest_Controller
│ ├── Yacht, Booking, Availability, Settings, Inquiry
│ ├── Email_Templates
│ └── listens: yacht_booking_created → send_booking_notification (admin email)
├── Admin (Singleton, admin-only)
│ ├── Booking, Availability, Yacht, Settings, Email_Templates, Inquiry
│ ├── Booking_List_Table → Booking, Availability, Settings
│ ├── Yacht_List_Table, Inquiry_List_Table
│ └── listens: yacht_booking_status_changed → send_customer_notification
├── Sync_Controller (Singleton)
│ ├── GCal_Service → OAuth_Handler
│ └── writes: Availability::mark_as_blocked
├── ICal_Import → Availability::mark_as_blocked
└── ICal_Feed (feed output)
```
## WP Hook Map
| Hook | Type | Registered by | Handler |
|------|------|---------------|---------|
| `plugins_loaded` (p10) | action | bootstrap | `yacht_booking_init()``Yacht_Booking::get_instance()` |
| `init` (p10) | action | Yacht_Booking | `register_post_types()` |
| `init` (p15) | action | Yacht_Booking | `register_shortcodes()` |
| `init` | action | bootstrap | `yacht_booking_load_textdomain()` |
| `wp_enqueue_scripts` | action | Yacht_Booking | `enqueue_frontend_assets()` (conditional) |
| `admin_enqueue_scripts` | action | Yacht_Booking | `enqueue_admin_assets()` (conditional) |
| `rest_api_init` | action | Yacht_Booking | `register_rest_routes()` |
| `elementor/widgets/register` | action | Yacht_Booking | `register_elementor_widgets()` |
| `admin_init` | action | Yacht_Booking | `add_custom_capabilities()` |
| `admin_menu` (p9) | action | Admin | `register_admin_menu()` |
| `admin_init` | action | Admin | `process_bulk_actions()`, `process_booking_actions()`, `process_yacht_save()`, `process_settings_save()`, `process_export_download()` |
| `admin_notices` | action | Admin | `display_admin_notices()` |
| `yacht_booking_created` | custom action | Rest_Controller | `send_booking_notification()` (admin email) |
| `yacht_booking_status_changed` | custom action | Admin | `send_customer_notification()` |
| `before_delete_post` | action | Sync_Controller | `on_booking_deleted()` (GCal event delete) |
| `wp_ajax_yacht_booking_manual_sync` | action | Sync_Controller | `ajax_manual_sync()` |
| `yacht_booking_sync_to_gcal` | cron action | Sync_Controller | `sync_booking_to_gcal()` |
| `yacht_booking_pull_from_gcal` | cron action (hourly) | Sync_Controller | `pull_from_gcal()` |
| `yacht_booking_ical_import` | cron action (hourly) | ICal_Import | `run_import()` |
| `init` | action | ICal_Feed | `add_rewrite_rules()` |
| `template_redirect` | action | ICal_Feed | `handle_feed_request()` |
## Key Data Flows
### Booking Creation (Frontend → REST → DB)
1. User picks dates on FullCalendar → `GET /availability/{yacht_id}``Availability::get_availability_calendar()`
2. User submits form → `POST /bookings` with `X-WP-Nonce` header
3. `Rest_Controller::create_booking()`: nonce check → `is_booking_enabled()``Availability::is_available()`
4. Price: `count_days() × get_price_per_day()`
5. `Booking::create()` → inserts CPT post + meta → fires `yacht_booking_created`
6. `Availability::mark_as_booked()` → inserts rows in `wp_yacht_availability`
7. Admin email sent via `Email_Templates`; JSON response to frontend
### Admin Booking Management
1. Bookings list → `Booking_List_Table::prepare_items()` (WP_Query)
2. Row action URLs: nonce-protected GET `?action=approve_booking_{id}`
3. `Admin::process_booking_actions()``Booking::update_status()` → fires `yacht_booking_status_changed`
4. `Admin::send_customer_notification()` → customer email
5. On cancel/delete: `Availability::clear_booking_availability($booking_id)` deletes rows by booking_id
6. PRG redirect back to list
### GCal Sync
1. Hourly WP Cron: `pull_from_gcal()``GCal_Service` → Google API → `Availability::mark_as_blocked()`
2. On booking create: scheduled single cron `yacht_booking_sync_to_gcal` → push to GCal
3. OAuth token refresh: `OAuth_Handler::get_access_token()` → auto-refresh if expired
## Error Handling Strategy
- REST endpoints return `WP_Error` with HTTP status codes
- Admin operations: `wp_die()` on capability failure; `?error=1` redirect on soft errors
- `Booking::create()` returns `false` on failure — caller checks
- GCal API: `is_wp_error()` check only; HTTP 4xx/5xx responses silently ignored
- No exceptions used anywhere
## Custom Capabilities
Added once to `administrator` role via `Yacht_Booking::add_custom_capabilities()`:
- `yacht_booking_manage_yachts`
- `yacht_booking_manage_bookings`
- `yacht_booking_manage_settings`

189
.paul/codebase/concerns.md Normal file
View File

@@ -0,0 +1,189 @@
# Technical Concerns
**Analysis Date:** 2026-05-05
**Revised:** 2026-05-05 — verified call sites; demoted false positives (see CHANGELOG at bottom)
Prioritized list of issues found during codebase analysis.
---
## CRITICAL — Security / Data Loss Risk
### C1. Race condition on booking creation — no atomic lock
**Files:** `includes/class-availability.php` (`is_available`), `api/class-rest-controller.php` (`create_booking` lines 326362)
Flow: `is_available()``Booking::create()``mark_as_booked()` — three separate non-transactional operations. Two concurrent POST requests for the same yacht and overlapping dates will both pass the check before either writes. The `UNIQUE KEY yacht_date (yacht_id, date)` will reject the second `mark_as_booked()` iteration silently, but both `yacht_booking` posts are already created. Admin sees two confirmed bookings for the same dates.
**Fix:** Wrap in a DB transaction with `SELECT ... FOR UPDATE` on availability rows, or insert a sentinel lock row first and check the result before proceeding.
---
### C2. OAuth credentials stored unencrypted in `wp_options`
**File:** `integrations/google-calendar/class-oauth-handler.php` lines 185187, 213215
`yacht_booking_gcal_credentials` (client_id, client_secret) and `yacht_booking_gcal_tokens` (access_token, refresh_token) stored as plaintext serialized arrays. Any plugin or user with DB read access can extract a working Google Calendar refresh token. If the database is compromised (via SQL injection elsewhere on the WordPress install or hosting breach), the Google Calendar account is fully exposed.
**Fix:** Encrypt with WordPress secret keys (`AUTH_KEY` etc.) before storing; decrypt on read.
---
## HIGH — Reliability
### H1. GCal API HTTP errors silently ignored
**Files:** `integrations/google-calendar/class-gcal-service.php`, `class-oauth-handler.php`
Every API call checks `is_wp_error()` but not the HTTP response code. A `401`, `403`, `429`, or `500` from Google is treated as success — `json_decode` runs, expected fields like `$body['id']` or `$body['access_token']` are absent, function returns `false`. All errors only logged when `WP_DEBUG === true`. In production: complete silence.
---
### H2. Token refresh has no concurrency protection
**File:** `integrations/google-calendar/class-oauth-handler.php` lines 107128
Simultaneous WP-Cron events both finding the token expired will both call `refresh_access_token()`. Google may invalidate the first new token when the second refresh request arrives. Result: permanently broken GCal connection requiring manual reauth.
**Fix:** Wrap refresh in a transient-based lock (`set_transient('gcal_refresh_lock', 1, 60)`), or use `wp_cache_add()` semantics.
---
### H3. `register_cron_actions()` may not be called — cron hooks fire with no registered callbacks
**File:** `integrations/google-calendar/class-sync-controller.php` lines 141153
The constructor registers `on_booking_created` etc., but `register_cron_actions()` (which registers the `yacht_booking_sync_to_gcal` action itself) is a separate static method. Need to verify it is called on every `init` / `plugins_loaded` request. If not, scheduled single events silently fire with no handler.
---
### H4. Single calendar for all yachts — GCal events apply to every yacht
**File:** `integrations/google-calendar/class-gcal-service.php` lines 196272
`get_calendar_id()` returns one shared option (`yacht_booking_gcal_calendar_id`). `pull_from_gcal()` calls `sync_from_gcal($yacht->ID)` for each yacht but each call fetches all events from the same calendar and blocks that yacht for all those dates. Multi-yacht sites will incorrectly block all yachts for every GCal event.
---
### H5. `tmp-fix-polish.php` in project root reveals dev paths
**File:** `tmp-fix-polish.php`
Contains only one line: a path string to another local project (`c:\visual studio code\projekty\jachty.pagedev.pl\tmp-fix-polish.php`). No PHP tag, no code. If deployed via FTP, the raw path string is publicly accessible at the URL. Should be deleted and excluded from deployment.
---
## MEDIUM — Maintainability / Correctness
### M1. Date validation missing on booking creation
**File:** `api/class-rest-controller.php` lines 116122
`start_date`/`end_date` use only `sanitize_text_field` — no `validate_callback`. An invalid date string like `"not-a-date"` reaches `new \DateTime()` in `Availability::count_days()` without try/catch, producing an uncaught fatal error visible to the public. The `/availability` endpoint validates dates correctly; `/bookings` does not.
**Fix:** Add `validate_callback` checking `\DateTime::createFromFormat('Y-m-d', $param) !== false`.
---
### M2. No pagination on `get_yachts` and `get_bookings`
**File:** `api/class-rest-controller.php` lines 222245, 429462
Both endpoints use `posts_per_page => -1`. Will degrade progressively as bookings accumulate.
---
### M3. N+1 query pattern in booking and yacht lists
**File:** `api/class-rest-controller.php` lines 442458
For each booking: `get_post($yacht_id)` called individually inside loop. For each yacht: three separate `get_post_meta()` calls per row. With 20 bookings: 60+ queries per response.
---
### M4. iCal importer ignores `STATUS:CANCELLED` events
**File:** `integrations/ical/class-ical-import.php` `parse_ics()` method
Parser reads UID, SUMMARY, DTSTART, DTEND but ignores STATUS. Cancelled events in the iCal feed are imported as blocks. The GCal service correctly skips `status: cancelled` events (line 347 of `class-gcal-service.php`) — parity needed in iCal importer.
---
### M5. `mark_as_blocked()` passes `null` booking_id with `%d` format
**File:** `includes/class-availability.php` lines 98109
`$wpdb->replace()` receives `null` with `%d` format specifier. In older MySQL/MariaDB this may insert `0` instead of `NULL`, making it impossible to distinguish a blocked row from one linked to booking ID 0.
---
### M6. OAuth redirect URI hardcoded to production domain
**File:** `integrations/google-calendar/class-oauth-handler.php` lines 40, 72
`$redirect_uri = 'https://jachty.pagedev.pl/wp-admin/...'` — literal string, not `admin_url()`. OAuth fails completely on staging or local dev. Comment acknowledges this is intentional but makes testing and domain migration fragile.
---
## LOW — Cleanup / Polish
### L1. Dead code: `Availability::mark_as_available()` has bug but is never called
**File:** `includes/class-availability.php` lines 123149
The function ignores its `$start_date`/`$end_date` parameters and runs `$wpdb->delete()` filtered only by `yacht_id` — which would wipe the entire availability table for that yacht.
**Verification:** `grep` across the plugin shows zero call sites. All code that handles cancellation/deletion uses `clear_booking_availability($booking_id)` (in `Admin`, `Rest_Controller`, `GCal_Service`, `ICal_Import`), which correctly filters by `booking_id`.
**Risk:** Trap for a future developer who reads the method name and decides to use it. Recommend either deleting the function or replacing its body with the commented-out correct implementation that already lives below it (lines 137146).
---
### L2. Dead form field: `yacht_booking_nonce` from `wp_nonce_field` is never read
**Files:** `frontend/class-shortcode.php:329`, `frontend/class-calendar-widget.php:429`
The booking form renders `wp_nonce_field('yacht_booking_submit', 'yacht_booking_nonce')` as a hidden input, but `calendar.js` (lines 336343) builds `formData` manually from named fields and never includes it. The actual nonce travels via `X-WP-Nonce` header from `yachtBookingData.nonce` (a `wp_create_nonce('wp_rest')` value), which matches what `Rest_Controller::create_booking()` verifies.
**Risk:** Code rot — looks like it's doing something but isn't. Either remove the `wp_nonce_field` call, or wire JS to send it as a fallback for the no-JS case (currently there is no no-JS fallback anyway).
---
### L3. Logging disabled in production
All `error_log()` calls gated on `WP_DEBUG === true`. GCal sync errors, token refresh failures, and iCal import problems are silently discarded in production. Consider a persistent log table or option-based error queue surfaced in admin.
---
### L4. No incremental DB migration path
**File:** `includes/class-installer.php`
No `upgrade()` method runs when `yacht_booking_version` in `wp_options` differs from `YACHT_BOOKING_VERSION`. `dbDelta()` adds missing columns but won't remove old ones or modify existing column types. Future schema changes risk silent failures on existing installs.
---
### L5. `yacht_booking` CPT exposed to default WP REST API
**File:** `includes/class-booking.php` line 48
`show_in_rest => true` registers the CPT at `/wp/v2/yacht_booking`. The plugin's own `/yacht-booking/v1/` namespace is the intended REST surface. Set `show_in_rest => false` unless there is a reason for the default WP exposure.
---
### L6. `Booking::create()` does not validate end > start
**File:** `includes/class-booking.php` lines 229230
A booking with `end_date` before `start_date` is stored without error. `get_date_range()` while-loop exits immediately, no availability rows are created, and the booking is invisible in the calendar but exists as a CPT post.
---
## CHANGELOG
**2026-05-05 (revision):** Subagent audit produced two false positives that were initially listed as CRITICAL. Verified by tracing call sites:
- **Old C1** (`mark_as_available` deletes all rows) → demoted to **L1**. The function is never called anywhere in the plugin; all cancellation paths use the correct `clear_booking_availability($booking_id)`. Remains a trap for future devs but no production impact.
- **Old C3** (POST /bookings nonce mismatch) → demoted to **L2**. The actual auth path (JS → `X-WP-Nonce` header → `wp_verify_nonce(..., 'wp_rest')`) is fully consistent. The `wp_nonce_field('yacht_booking_submit', ...)` in shortcode/widget renders a hidden input that JS never reads — dead form field, not a security gap.
Race condition (old C2 → C1) and unencrypted OAuth tokens (old C4 → C2) remain valid CRITICAL items.

View File

@@ -0,0 +1,110 @@
# Coding Conventions
**Analysis Date:** 2026-05-05
## PHP Style
**Standard:** WordPress Coding Standards (not PSR-12).
- 1 tab indentation
- `array()` long-form always — never `[]` short syntax in plugin code
- Spaces inside control structure parens: `if ( $condition )`, `foreach ( $items as $item )`
- No spaces inside function call parens: `get_post_meta( $id, '_key', true )`
- Every PHP file starts with direct-access guard:
```php
if ( ! defined( 'ABSPATH' ) ) { exit; }
```
- File-level docblock with `@package YachtBooking`
## Naming
| Thing | Convention | Example |
|-------|-----------|---------|
| Classes | PascalCase (underscore for multi-word) | `Rest_Controller`, `Yacht_Booking` |
| Methods | snake_case | `get_yacht_id()`, `mark_as_booked()` |
| Variables | snake_case | `$booking_id`, `$start_date` |
| Constants | SCREAMING_SNAKE_CASE | `YACHT_BOOKING_VERSION` |
| Post meta keys | underscore-prefixed, namespaced | `_booking_start_date`, `_yacht_capacity` |
| WP option keys | `yacht_booking_` prefix | `yacht_booking_version` |
| Boolean getters | `is_` / `has_` prefix | `is_available()`, `has_yacht_calendar_widget()` |
| Static getters | `get_{thing}($id)` | `Booking::get_customer_name($id)` |
| Static updaters | `update_{thing}($id, $value)` | `Yacht::update_capacity($id, $v)` |
## Class Patterns
**Singleton** — for hook-registering classes (must only run once):
```php
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) { self::$instance = new self(); }
return self::$instance;
}
private function __construct() { /* register hooks here */ }
```
Used by: `Yacht_Booking`, `Admin`, `Shortcode`, `Sync_Controller`
**Static utility** — for pure data accessors with no instance state:
```php
public static function get_customer_name( $booking_id ) {
return get_post_meta( $booking_id, '_booking_customer_name', true );
}
```
Used by: `Booking`, `Yacht`, `Availability`, `Settings`, `Email_Templates`
**All WP hook registration goes in constructor or `init_hooks()` method.**
## Security Patterns
- **Nonces in admin forms:** `wp_nonce_field()` on output, `check_admin_referer()` on submit
- **Nonces in REST endpoints:** `X-WP-Nonce` header verified with `wp_verify_nonce($header, 'wp_rest')`
- **iCal feed:** per-yacht token in `_yacht_ical_token` meta, compared with `hash_equals()`
- **Admin REST endpoints:** `current_user_can('yacht_booking_manage_bookings')`
- **Sanitization on input:** `sanitize_text_field()`, `sanitize_email()`, `absint()`, `wp_kses_post()`
- **Escaping on output:** `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()`
- **All `$wpdb` queries:** use `$wpdb->prepare()` — no raw SQL
## WordPress Integration Patterns
- CPT registration via static `register()` method called by orchestrator
- Options: `add_option()` (idempotent) on activation, `get_option()` / `update_option()` for runtime
- Capability checks use custom caps (`yacht_booking_manage_bookings`), not `manage_options`
- Admin form submissions: POST → `admin_init` handler → `wp_safe_redirect()` (PRG pattern)
- Error feedback: `?error=message` query string + `admin_notices` hook display
## JavaScript Style
- **Pattern:** IIFE `(function($) { 'use strict'; ... })(jQuery)` in both JS files
- **Variable naming:** camelCase; jQuery-wrapped objects prefixed with `$` (`$form`, `$submitBtn`)
- **`calendar.js`:** Named inner functions (`initYachtCalendar`, `formatDate`, `escapeHtml`); uses `const`/`let`
- **`admin.js`:** Object-literal module pattern (`const YachtBookingAdmin = { init, bindEvents, handleManualSync }`)
- **Data bridge:** `wp_localize_script()` injects `yachtBookingData` (apiUrl, nonce, bookingEnabled, i18n) and `yachtBookingAdmin`
- **Error display:** Inline HTML into `.yacht-booking-response` / `.yacht-inquiry-response` divs; messages from `yachtBookingData.i18n`
## CSS / Markup
- BEM-like kebab-case with `yacht-` prefix: `.yacht-calendar-wrapper`, `.yacht-booking-form-container`
- Modifier suffixes: `.yacht-calendar-view-only`, `.yacht-legend-swatch-past`
- JS state classes: `.is-active`, `.yacht-day-available`, `.yacht-day-booked`
- Mobile-first approach
## Internationalisation
- All user-visible strings: `__()`, `esc_html__()`, `esc_attr_e()` with domain `'yacht-booking'`
- Strings with variables: `printf( esc_html__( 'Text %s', 'yacht-booking' ), esc_html( $var ) )` — never concatenation
- Translators hint: `/* translators: %s: yacht name */` before `printf`
## Documentation
- Public/protected methods always get docblocks; private helpers sometimes omitted
- Inline block comments label logical sections: `// Get yacht title`, `// Save booking meta`
- HTML comments label template sections in Polish: `<!-- Nazwa jachtu -->`
- JS functions get JSDoc blocks: `/** Format date to YYYY-MM-DD */`
- PHPDoc generics notation for complex types: `@return array<int, array<string, mixed>>`
## Error Handling
- PHP: return `false` or `WP_Error` on failure
- REST: return `new \WP_Error( 'code', message, array( 'status' => 4xx ) )`
- No exceptions used anywhere in the plugin
- GCal API: `is_wp_error()` check only — HTTP error codes are NOT checked (known gap)
- Logging: `error_log()` with prefixes only when `WP_DEBUG === true`; nothing in production

View File

@@ -0,0 +1,120 @@
# External Integrations
**Analysis Date:** 2026-05-05
## Google Calendar API v3
**Purpose:** Bidirectional sync — bookings push to Google Calendar as events; Google Calendar events pull back as availability blocks.
**Implementation:** Native PHP, no SDK. Uses WordPress HTTP API (`wp_remote_post`, `wp_remote_get`, `wp_remote_request`).
**Files:**
- `integrations/google-calendar/class-gcal-service.php` — API calls
- `integrations/google-calendar/class-oauth-handler.php` — OAuth 2.0 token management
- `integrations/google-calendar/class-sync-controller.php` — Cron orchestration (Singleton)
**Authentication:** OAuth 2.0 with `offline` access (refresh token).
- Auth endpoint: `https://accounts.google.com/o/oauth2/v2/auth`
- Token endpoint: `https://oauth2.googleapis.com/token`
- Scope: `https://www.googleapis.com/auth/calendar`
- Credentials stored in `wp_options` key `yacht_booking_gcal_credentials`
- Tokens stored in `wp_options` key `yacht_booking_gcal_tokens`
- Calendar ID stored in `yacht_booking_gcal_calendar_id` (defaults to `'primary'`)
- **OAuth redirect URI hardcoded:** `https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1`
**Outbound API calls:**
| Method | URL | Purpose |
|--------|-----|---------|
| POST | `.../calendars/{id}/events` | Create event |
| PATCH | `.../calendars/{id}/events/{eventId}` | Update event |
| DELETE | `.../calendars/{id}/events/{eventId}` | Delete event |
| GET | `.../calendars/{id}/events` | List events (pull sync) |
| GET | `.../users/me/calendarList` | List calendars |
| POST | `https://oauth2.googleapis.com/token` | Token exchange/refresh |
| GET | `https://www.googleapis.com/oauth2/v2/userinfo` | Get connected email |
**Sync model:** Pull-only for external → local (hourly WP Cron `yacht_booking_pull_from_gcal`). Push for local → GCal on `yacht_booking_created` / `yacht_booking_status_changed` (scheduled single cron events).
**Limitations:**
- Single shared calendar for all yachts — GCal events apply to every yacht, not per-yacht
- No incoming webhooks — polling model only
- No retry logic on API failure
## iCal Integration
### Export (Feed)
**Purpose:** Generate `.ics` file per yacht for subscription by external clients (other booking systems, personal calendars).
**File:** `integrations/ical/class-ical-feed.php`
**URL pattern:** `{home_url}/yacht-ical/{yacht_id}/{token}.ics`
- Token stored in `_yacht_ical_token` post meta (24-char alphanumeric, auto-generated)
- Verified with `hash_equals()` — timing-safe
- Returns `text/calendar` with all non-cancelled bookings for that yacht
- Regenerate token: `ICal_Feed::regenerate_token($yacht_id)`
### Import (Subscribe)
**Purpose:** Subscribe to external iCal URLs per yacht; import events as availability blocks (hourly cron).
**File:** `integrations/ical/class-ical-import.php`
**Config:** Per-yacht `_yacht_ical_import_url` post meta (set via `ICal_Import::set_import_url()`)
**Behavior:** Fetches URL via `wp_remote_get()` (30-second timeout, SSL verification), parses VEVENT components, creates `yacht_booking` posts with `_booking_source = 'ical_import'`, marks dates as blocked via `Availability::mark_as_blocked()`.
**Limitation:** Does not check VEVENT `STATUS` property — cancelled events in the feed are imported as blocks.
## jsDelivr CDN
**Purpose:** FullCalendar library loaded from CDN (no local copy).
| Asset | URL |
|-------|-----|
| FullCalendar JS | `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js` |
| FullCalendar CSS | `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css` |
| Polish locale | `https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.10/locales/pl.global.min.js` |
Enqueued in `Yacht_Booking::enqueue_frontend_assets()`, conditional on page content.
## Email
**Provider:** WordPress `wp_mail()` only — no Mailgun, SendGrid, or other transactional service.
**Templates:** `includes/class-email-templates.php` — templates stored in `wp_options` as tagged strings, compiled with `get_booking_template_data()`.
**When sent:**
- On booking creation: admin notification via `Rest_Controller::send_booking_notification()` (hooked on `yacht_booking_created`)
- On status change: customer notification via `Admin::send_customer_notification()` (hooked on `yacht_booking_status_changed`)
- On inquiry create: admin + customer emails via `Inquiry::send_emails()` (built inline, not using Email_Templates)
**Headers:** `From:` pulled from `yacht_booking_email_from` option; `Reply-To:` set explicitly.
## REST API (Internal)
Namespace: `yacht-booking/v1` — base: `/wp-json/yacht-booking/v1/`
| Method | Endpoint | Auth | Handler |
|--------|----------|------|---------|
| GET | `/yachts` | Public | `get_yachts()` |
| GET | `/yachts/{id}` | Public | `get_yacht()` |
| GET | `/availability/{yacht_id}?start=Y-m-d&end=Y-m-d` | Public | `get_availability()` |
| POST | `/bookings` | Public + `X-WP-Nonce` | `create_booking()` |
| POST | `/inquiries` | Public + `X-WP-Nonce` | `create_inquiry()` |
| GET | `/bookings` | Admin cap | `get_bookings()` |
| PUT | `/bookings/{id}/status` | Admin cap | `update_booking_status()` |
All registered in `api/class-rest-controller.php`.
## Monitoring & Observability
- No error tracking service (no Sentry, Bugsnag)
- `error_log()` used with prefixes: `[Yacht Booking - GCal]`, `[Yacht Booking - GCal Sync]`, `[Yacht Booking - iCal]`
- Logging only fires when `WP_DEBUG === true` — silent in production
## Deployment
- **Method:** FTP via VS Code ftp-kr extension (`.vscode/ftp-kr.json`)
- **CI/CD:** None — fully manual
- **Production URL:** `https://jachty.pagedev.pl` (hardcoded in GCal OAuth redirect)

View File

@@ -0,0 +1,52 @@
# Codebase Overview
**Project:** jachty3.pagedev.pl — Yacht Booking System
**Analyzed:** 2026-05-05
## What This Is
A custom WordPress plugin (`yacht-booking-system`) that provides yacht rental booking management with a FullCalendar frontend, REST API backend, Google Calendar bidirectional sync, and iCal feed export/import. Built for a Polish yacht charter company at `https://jachty.pagedev.pl`.
## Primary Entry Point
`wp-content/plugins/yacht-booking-system/yacht-booking-system.php`
- Defines 5 plugin constants (`YACHT_BOOKING_VERSION`, `YACHT_BOOKING_PLUGIN_DIR`, etc.)
- Registers a custom `spl_autoload_register` for the `YachtBooking\` namespace → `includes/class-*.php`
- Fires `Yacht_Booking::get_instance()` on `plugins_loaded`
- Activation hook: runs `Installer::install()`, schedules GCal and iCal cron jobs, flushes rewrite rules
## Core Data Model
Two parallel stores:
1. **WordPress CPTs** (in `wp_posts` + `wp_postmeta`):
- `yacht` — meta: capacity, price_per_day, gcal_id, features, ical_token, ical_import_url
- `yacht_booking` — meta: yacht_id, start_date, end_date, status, customer_*, total_price, gcal_event_id, source
- `yacht_inquiry` — meta: yacht_id, customer_*, preferred_dates, message (hidden from WP admin UI)
2. **Custom availability table** `wp_yacht_availability`:
- One row per yacht-date pair; only `booked` and `blocked` rows stored; absence = available
- Unique key on `(yacht_id, date)`
## User-Facing Features
- FullCalendar month view embedded via `[yacht_calendar yacht_id="X"]` shortcode or Elementor widget
- Booking form (date selection → customer details → submission via REST)
- Inquiry form (non-binding contact, sends email to admin)
- iCal feed per yacht at `{home_url}/yacht-ical/{yacht_id}/{token}.ics`
## Admin Features
- Custom admin menu with 6 pages: Bookings list, Inquiries list, Yachts list/edit, Settings, Google Calendar
- Approve/Cancel/Delete bookings with PRG pattern
- CSV export of bookings
- Google Calendar bidirectional sync (OAuth 2.0, manual + hourly cron)
- iCal import from external URLs per yacht (hourly cron)
- Email notifications: admin on booking create, customer on status change
## Language / Locale
UI language: Polish. All strings use `__()` / `esc_html__()` with `yacht-booking` text domain.
## Team / Deployment
Single developer. FTP deploy via ftp-kr VS Code extension. No CI/CD pipeline. Production: `https://jachty.pagedev.pl`. No Docker, no local environment config detected.

65
.paul/codebase/stack.md Normal file
View File

@@ -0,0 +1,65 @@
# Technology Stack
**Analysis Date:** 2026-05-05
## Languages
- **PHP 7.4+** — all server-side plugin logic (`Requires PHP: 7.4` in plugin header)
- **JavaScript (ES5/IIFE)** — frontend calendar + booking form (`calendar.js`), admin panel JS (`admin.js`)
- **SCSS** — frontend calendar styles (source at `frontend/assets/css/calendar.scss`; must be manually compiled)
- **SQL** — custom table queries via `$wpdb->prepare()`
## Runtime
- **WordPress 6.0+** (`Requires at least: 6.0`)
- **Elementor Pro** — optional; plugin checks `class_exists('\Elementor\Plugin')` and degrades gracefully
- **MySQL** — custom table + WordPress standard tables
## Key Dependencies
| Dependency | Version | How Loaded |
|-----------|---------|-----------|
| FullCalendar | 6.1.10 | jsDelivr CDN |
| FullCalendar Polish locale | 6.1.10 | jsDelivr CDN |
| jQuery | WP bundled | WordPress core |
| Google Calendar API v3 | — | Native HTTP (no SDK) |
**CDN URLs:**
- `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js`
- `https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css`
- `https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.10/locales/pl.global.min.js`
## Plugin Constants
Defined in `yacht-booking-system.php` (lines 2327):
```php
YACHT_BOOKING_VERSION = '1.0.0'
YACHT_BOOKING_PLUGIN_FILE = __FILE__
YACHT_BOOKING_PLUGIN_DIR = plugin_dir_path(__FILE__)
YACHT_BOOKING_PLUGIN_URL = plugin_dir_url(__FILE__)
YACHT_BOOKING_PLUGIN_BASENAME = plugin_basename(__FILE__)
```
## Autoloading
Custom `spl_autoload_register` (lines 3256, `yacht-booking-system.php`):
- Namespace prefix: `YachtBooking\`
- Maps to: `YACHT_BOOKING_PLUGIN_DIR . 'includes/'`
- Rule: `YachtBooking\Foo_Bar``includes/class-foo-bar.php`
- **Only covers `includes/`** — integration and API classes loaded via explicit `require_once`
## Configuration Storage
- No `.env` file — all runtime config in `wp_options`
- Key options: `yacht_booking_gcal_credentials`, `yacht_booking_gcal_tokens`, `yacht_booking_gcal_calendar_id`, `yacht_booking_gcal_sync_enabled`, `yacht_booking_default_status`, `yacht_booking_enable_notifications`
## Build Pipeline
None. No Composer, no npm, no webpack/vite/esbuild. Assets are static hand-authored files deployed via FTP (`ftp-kr` VS Code extension, config: `.vscode/ftp-kr.json`).
## Platform Requirements
- **Dev:** VS Code + ftp-kr, PHP 7.4+ CLI (`php -l <file>` for syntax checks)
- **Production:** PHP 7.4+, MySQL, WordPress 6.0+, shared hosting or VPS
- **Note:** OAuth redirect URI hardcoded to `https://jachty.pagedev.pl` — staging/local OAuth will fail

View File

@@ -0,0 +1,94 @@
# Codebase Structure
**Analysis Date:** 2026-05-05
## Directory Layout
```
wp-content/plugins/yacht-booking-system/
├── yacht-booking-system.php # Plugin entry: constants, autoloader, hooks
├── includes/ # Core PHP classes (autoloaded via spl_autoload_register)
│ ├── class-yacht-booking.php # Main Singleton orchestrator
│ ├── class-yacht.php # Yacht CPT + static meta accessors
│ ├── class-booking.php # Booking CPT + static meta accessors
│ ├── class-inquiry.php # Inquiry CPT + static meta accessors + email sending
│ ├── class-availability.php # wp_yacht_availability table read/write
│ ├── class-installer.php # DB table creation, default options
│ ├── class-settings.php # wp_options accessors + formatting helpers
│ └── class-email-templates.php # Email template storage, compilation, tag replacement
├── api/ # REST API (loaded via require_once, not autoloader)
│ └── class-rest-controller.php # All REST routes under yacht-booking/v1
├── admin/ # WP admin panel (is_admin() gate)
│ ├── class-admin.php # Admin Singleton: menu, form handling, CSV export
│ ├── class-booking-list-table.php # WP_List_Table for bookings
│ ├── class-yacht-list-table.php # WP_List_Table for yachts
│ ├── class-inquiry-list-table.php # WP_List_Table for inquiries
│ ├── views/
│ │ └── yacht-edit.php # Add/edit yacht form template
│ └── assets/
│ ├── css/admin.css
│ └── js/admin.js
├── frontend/
│ ├── class-calendar-widget.php # Elementor widget (yacht-calendar)
│ ├── class-shortcode.php # [yacht_calendar] shortcode, Singleton
│ └── assets/
│ ├── css/calendar.css # Compiled from calendar.scss
│ ├── css/calendar.scss # SCSS source (manual compile required)
│ └── js/calendar.js # IIFE jQuery; consumes REST API via yachtBookingData
├── integrations/
│ ├── google-calendar/ # Loaded via explicit require_once (not autoloader)
│ │ ├── class-oauth-handler.php # OAuth 2.0 token storage + refresh
│ │ ├── class-gcal-service.php # Google Calendar API HTTP calls
│ │ └── class-sync-controller.php # Cron sync orchestration, Singleton
│ └── ical/
│ ├── class-ical-feed.php # Generate iCal feed, serve via rewrite rule
│ └── class-ical-import.php # Import external iCal URLs via cron
└── languages/ # .pot / .po / .mo for yacht-booking text domain
```
**Project root (WordPress install root — not plugin directory):**
```
/
├── test-yacht-plugin.php # Manual smoke test (run in browser as admin)
├── test-api-availability.php # Manual integration test (run in browser as admin)
└── tmp-fix-polish.php # Leftover artifact — single path string, no PHP code
```
## Naming Rules
| Thing | Convention | Example |
|-------|-----------|---------|
| PHP class files | `class-{kebab-case}.php` | `class-rest-controller.php` |
| PHP class names | `PascalCase` (with underscores) | `Rest_Controller`, `Yacht_Booking` |
| Directories | lowercase kebab-case | `google-calendar/`, `yacht-booking-system/` |
| Autoloader mapping | `YachtBooking\Foo_Bar``includes/class-foo-bar.php` | — |
| Sub-namespaces (integrations) | `YachtBooking\Integrations\GoogleCalendar` | — |
## Where to Add New Code
**New core feature:**
- File: `includes/class-{feature}.php`, namespace `YachtBooking\`
- Load: relies on autoloader automatically (for `includes/` only)
**New REST endpoint:**
- Add method + route registration to `api/class-rest-controller.php`
- Public: `'permission_callback' => '__return_true'` + nonce check inside handler
- Admin: `'permission_callback' => array($this, 'can_manage_bookings')`
**New admin page:**
- Add `add_submenu_page()` in `Admin::register_admin_menu()`
- Add `render_{page}()` method in `admin/class-admin.php`
- Add form processing method hooked on `admin_init` in `Admin::__construct()`
- View HTML: extract to `admin/views/{page}.php`
**New external integration:**
- Create `integrations/{service-name}/`
- Follow pattern: `class-{service}-service.php` + `class-sync-controller.php`
- Register cron: `static setup_cron()` + `static clear_cron()`, call from activation/deactivation hooks
- Load via `require_once` in `Yacht_Booking::load_dependencies()`
**New setting:**
- Add default in `Installer::create_options()`
- Add typed accessor in `Settings` class
- Add UI in `Admin::render_general_settings()` or new settings tab
- Save in `Admin::save_settings()`

106
.paul/codebase/testing.md Normal file
View File

@@ -0,0 +1,106 @@
# Testing
**Analysis Date:** 2026-05-05
## Framework
None. No PHPUnit, no Jest, no Vitest. No automated test runner configured.
**Only testing mechanism:** Two manual PHP scripts in the project root, run in-browser while logged in as admin against the live database.
## Test Files
| File | Type | Purpose |
|------|------|---------|
| `test-yacht-plugin.php` | Smoke test | Verifies plugin environment is set up correctly |
| `test-api-availability.php` | Integration test | Exercises REST endpoint + Availability class |
**How to run:**
```
# Syntax check (run after every PHP edit):
php -l wp-content/plugins/yacht-booking-system/includes/class-booking.php
# Browser smoke test (must be logged in as admin):
http://jachty.pagedev.local/test-yacht-plugin.php
# Browser integration test:
http://jachty.pagedev.local/test-api-availability.php
```
## What Is Tested
### test-yacht-plugin.php (Smoke Test)
1. Plugin file exists on disk
2. Plugin is active (`is_plugin_active()`)
3. `wp_yacht_availability` table exists
4. CPTs registered (`yacht`, `yacht_booking`)
5. Plugin options set (`yacht_booking_version`, `yacht_booking_installed_at`)
6. Custom capabilities assigned to `administrator` role
7. REST namespace `yacht-booking/v1` registered + route list
8. Admin menu `yacht-bookings` slug present
9. Yacht CPT CRUD: `wp_insert_post()` + meta + `wp_delete_post()` (cleanup included)
### test-api-availability.php (Integration Test)
1. Fetch first yacht from DB
2. Call `/availability/{yacht_id}` via `wp_remote_get()` — check HTTP 200, count statuses
3. Direct call `Availability::get_availability_calendar()` — display in HTML table
4. Direct call `Availability::is_available()` on +7 to +10 day range
5. `mark_as_booked()` with fake booking ID 999 → re-check → `clear_booking_availability(999)` → re-check (full CRUD cycle)
## What Is NOT Tested
- `POST /bookings` — full booking creation flow via REST never tested
- Email sending (`send_booking_notification`, `send_customer_notification`, `Inquiry::send_emails`)
- Google Calendar OAuth flow
- GCal sync (push and pull)
- iCal import and export
- Admin form processing (`process_yacht_save`, `process_booking_actions`, `save_settings`)
- Nonce verification in REST endpoints
- Status transitions (`pending``confirmed``cancelled`) and side effects
- `Shortcode::render_calendar()` output
- `Calendar_Widget::render()` output
- All JavaScript behavior
- Concurrent booking race conditions
- `mark_as_available()` bug (deletes all rows for yacht — see concerns.md)
## Test Pattern
```php
// Access guard
require_once __DIR__ . '/wp-load.php';
if (!current_user_can('manage_options')) { die('Admin only'); }
// Numbered HTML sections
echo '<h2>1. Section Name</h2>';
// Visual pass/fail — no assertions that throw
if ($condition) {
echo '✅ Thing works<br>';
} else {
echo '❌ Thing broken<br>';
die(); // hard stop only on fatal blockers
}
// Data output
echo '<details><summary>Raw data</summary><pre>';
print_r($data);
echo '</pre></details>';
```
## Mocking
None. All tests hit the live WordPress database. Side effects are real:
- `test-yacht-plugin.php`: creates and deletes a test yacht post
- `test-api-availability.php`: inserts availability rows with `booking_id=999`, then deletes them
## Coverage Tooling
None. No coverage measurement.
## Style Notes in Test Files
Intentional deviations from plugin code conventions (acceptable for dev-only scripts):
- Short array syntax `[]` used (plugin uses `array()`)
- No namespaces
- Partial output escaping — `$yacht->post_title` sometimes raw-echoed
- One raw `$wpdb->get_var()` without `prepare()` (`test-yacht-plugin.php:49`) — safe here as table name is not user input

33
.paul/config.md Normal file
View File

@@ -0,0 +1,33 @@
# Project Config
**Project:** jachty3.pagedev.pl
**Created:** 2026-05-05
## Project Settings
```yaml
project:
name: jachty3.pagedev.pl
version: 1.0.0
```
## Integrations
### SonarQube
```yaml
sonarqube:
enabled: false
reason: "User declined during init"
```
## Preferences
```yaml
preferences:
auto_commit: false
verbose_output: false
```
---
*Config created: 2026-05-05*

View File

@@ -0,0 +1,28 @@
{"ts":"2026-05-05T21:35:55Z","tool":"Bash","cmd":"mkdir -p .paul/phases .paul/codebase\",\"description\":\"Create PAUL directory structure\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOutputExpecte","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:39:20Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:39:38Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:39:55Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:40:21Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\CLAUDE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:40:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\config.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:42:03Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interr","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:42:13Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interr","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:42:16Z","tool":"Bash","cmd":"powershell -Command \"Get-Item 'C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\tmp-fix-polish.php' | Select-Object FullName, Length, Attributes, LinkType, Target | Format-List\"\",\"des","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:42:26Z","tool":"Bash","cmd":"powershell -Command \"Get-Content 'C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\tmp-fix-polish.php'\"\",\"description\":\"Read tmp-fix-polish.php content\"},\"tool_response\":{\"stdou","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:42:33Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\STACK.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:43:00Z","tool":"Bash","cmd":"mkdir -p \"/c/visual studio code/projekty/jachty3.pagedev.pl/.planning/codebase\" && echo \"ok\"\",\"description\":\"Create .planning/codebase directory\"},\"tool_response\":{\"stdout\":\"ok\",\"stder","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:43:18Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\ARCHITECTURE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:43:41Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\INTEGRATIONS.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:44:00Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\CONVENTIONS.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:44:06Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\STRUCTURE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:44:46Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.planning\\\\codebase\\\\TESTING.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:45:40Z","tool":"Bash","cmd":"mkdir -p \"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\"\",\"description\":\"Create .paul/codebase directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:46:03Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\overview.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:46:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\stack.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:47:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:47:32Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\structure.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:48:12Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\conventions.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:48:45Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\integrations.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:49:18Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\testing.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:50:11Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\concerns.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:54:43Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\codebase\\\\concerns.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T21:59:23Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/phases/09-finalizacja\" 2>&1 && ls \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/phases/\"\",\"description\":\"Create pha","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}

View File

@@ -0,0 +1,33 @@
{"ts":"2026-05-05T22:01:07Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-01-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:01:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:01:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:01:34Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:03:50Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:04:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\js\\\\calendar.js","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:04:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:04:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:04:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:04:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:05:00Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:05:05Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php\" 2>&1\",\"description\":\"PHP syntax check\"},\"tool_response\":{","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:05:13Z","tool":"Bash","cmd":"node -c \"C:/visual studio code/projekty/jachty3.pagedev.pl/wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js\" 2>&1; echo \"exit=$?\"\",\"description\":\"JS syntax check\"},\"too","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:07:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:07:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:07:50Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\test-add-booking.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:07:56Z","tool":"Bash","cmd":"php -l \"C:/visual studio code/projekty/jachty3.pagedev.pl/test-add-booking.php\" 2>&1\",\"description\":\"PHP syntax check\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in C:/visual st","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:10:25Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:10:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:10:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:10:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:11:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:11:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:12:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:12:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\class-calendar-widget.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:12:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.scss","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:12:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\frontend\\\\assets\\\\css\\\\calendar.css","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:13:51Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/jachty3.pagedev.pl\" && git diff --stat HEAD -- wp-content/plugins/yacht-booking-system/ 2>&1\",\"description\":\"stats of modified files\"},\"tool_response\":{\"st","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:15:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:15:24Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:15:30Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:15:41Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/changelog/\"\",\"description\":\"Create changelog dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"i","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
{"ts":"2026-05-05T22:15:59Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}

View File

@@ -0,0 +1,262 @@
---
phase: 09-finalizacja
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js
- wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss
- wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css
- wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
autonomous: false
delegation: off
---
<objective>
## Goal
Wprowadzić wizualne oznaczenie „pół-dnia" na pierwszym i ostatnim dniu rezerwacji w kalendarzu frontowym (FullCalendar) oraz dostroić domyślną kolorystykę widgetu Elementor `yacht-calendar` do palety strony jachty3.pagedev.pl (granat + czerwony akcent).
## Purpose
Klient zgłosił, że obecna wizualizacja kalendarza nie odzwierciedla rzeczywistego rytmu wynajmu jachtów: jacht jest wydawany danego dnia od ustalonej godziny odbioru (po południu) i zwracany w dniu zakończenia rano. Pełne zablokowanie skrajnych dni rezerwacji wprowadza w błąd potencjalnych klientów — sugeruje, że dzień odbioru/zwrotu jest niedostępny, podczas gdy w praktyce można wtedy rozpocząć/zakończyć inną rezerwację. Dodatkowo obecna paleta zieleń+czerwień nie pasuje do granatowo-czerwonego brandingu strony.
## Output
- Zmodyfikowany kalendarz frontowy (JS + CSS) renderujący skos „pół-zajętego" dnia dla pierwszego i ostatniego dnia każdej ciągłej blokady (booking, GCal, iCal)
- Zaktualizowane domyślne kolory widgetu Elementor (`primary`, `available`, `booked`) dopasowane do palety strony
- Zachowana kompatybilność z istniejącymi instancjami widgetu (override przez ustawienia Elementora dalej działa)
</objective>
<context>
<clarifications>
- **Half-day** — Który dzień ma być wypełniony „w połowie"?
→ Odpowiedź: Pierwszy i ostatni dzień rezerwacji. Wizualnie: dzień startu = lewa połowa wolna, prawa połowa zajęta (odbiór po południu); dzień końca = lewa połowa zajęta, prawa połowa wolna (zwrot rano).
- **Kolorystyka** — Skąd brać kolory dopasowane do strony?
→ Odpowiedź: Zmienić domyślne wartości w `Calendar_Widget` (Elementor). Istniejące instancje będą mogły zachować swoje ustawienia (override) lub zresetować do nowych defaultów.
- **Blocked GCal** — Czy half-day dotyczy też blokad zewnętrznych (Google Calendar / iCal, bez `booking_id`)?
→ Odpowiedź: Tak — analogicznie. Pierwszy i ostatni dzień ciągłego pasma `blocked` także jest pół-zajęty. Detekcja na podstawie ciągłości statusu (poprzedni/następny dzień ma inny status lub inny `booking_id`).
- **Scope** — Czy to plan fazy 9 czy hotfix?
→ Odpowiedź: Plan 09-01 fazy 9 (UX/UI polish). Plany 09-02 (settings page), 09-03 (security audit), 09-04 (testy/tłumaczenia/dokumentacja) idą później.
- **Selectability ostatniego dnia** (wynika z odpowiedzi 1) — Czy pierwszy/ostatni dzień rezerwacji ma być klikalny dla nowej rezerwacji?
→ Odpowiedź: Wizualnie tak (skos pokazuje, że pół dnia jest wolne), natomiast logika walidacji rezerwacji **pozostaje bez zmian** w tym planie — overlap dalej blokuje wybór skrajnych dni. Zmiana logiki to osobna decyzja biznesowa (godziny check-in/out, polityka rezerwacji) i będzie tematem oddzielnego planu jeśli klient tego zechce.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
## Source Files
@wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js
@wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss
@wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
@wp-content/plugins/yacht-booking-system/includes/class-availability.php
</context>
<acceptance_criteria>
## AC-1: Skos na pierwszym dniu rezerwacji
```gherkin
Given jacht ma rezerwację w terminie 2124 maja
When użytkownik otwiera kalendarz tego jachtu
Then dzień 21 maja renderuje się z lewą połową w kolorze "available" i prawą połową w kolorze "booked" (gradient po przekątnej z lewego dołu do prawej góry, lub liniowo 50/50)
And dni 22 i 23 maja są w pełni w kolorze "booked"
```
## AC-2: Skos na ostatnim dniu rezerwacji
```gherkin
Given jacht ma rezerwację w terminie 2124 maja
When użytkownik otwiera kalendarz tego jachtu
Then dzień 24 maja renderuje się z lewą połową w kolorze "booked" i prawą połową w kolorze "available"
```
## AC-3: Detekcja ciągłości — różne booking_id obok siebie
```gherkin
Given jacht ma rezerwację A (1518 maja, booking_id=10) i rezerwację B (1922 maja, booking_id=11)
When kalendarz renderuje maj
Then dzień 18 jest "ostatni" rezerwacji A (skos prawy = available) ale jeśli 19 jest pierwszym B, na styku 18/19 oba dni mają skosy w przeciwnych kierunkach
And dni 16, 17 (środek A) oraz 20, 21 (środek B) są w pełni booked
```
## AC-4: Skos także dla blokad GCal/iCal
```gherkin
Given jacht ma blokadę zewnętrzną (status=blocked, booking_id=null) w dniach 57 czerwca
When kalendarz renderuje czerwiec
Then dzień 5 czerwca = lewa połowa available, prawa booked
And dzień 7 czerwca = lewa booked, prawa available
And dzień 6 czerwca = w pełni booked
```
## AC-5: Nowa paleta domyślna widgetu
```gherkin
Given administrator dodaje świeży widget "Yacht Calendar" w Elementorze
When otwiera ustawienia stylu
Then domyślne kolory to: primary = granat strony (np. #0a2a5e), available = jasny / transparentny (np. #f5f9ff lub rgba), booked = czerwień strony (np. #d63638)
And istniejące widgety (z zapisanymi własnymi kolorami) zachowują swoje wartości nie są nadpisywane
```
## AC-6: Walidacja wyboru dat niezmieniona
```gherkin
Given jacht ma rezerwację 2124 maja
When użytkownik próbuje wybrać 2426 maja w kalendarzu
Then system blokuje wybór (alert "Wybrane daty zawierają niedostępne terminy")
# Zmiana logiki check-in/check-out poza zakresem tego planu.
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Wzbogacić REST/JS o detekcję krawędzi rezerwacji i wyrenderować skos</name>
<files>
wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js,
wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss,
wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css
</files>
<action>
**Detekcja krawędzi (klient-side, w `events` callback):**
1. Po pobraniu danych z `/availability/{yacht_id}` posortować dni po dacie.
2. Dla każdego dnia o statusie != 'available' obliczyć:
- `isStart` = poprzedni dzień (date - 1) jest 'available' LUB ma inny `booking_id` LUB ma inny status (booked vs blocked).
- `isEnd` = następny dzień (date + 1) j.w.
- Jeżeli zarówno `isStart` jak i `isEnd` to dzień jest "single" (1-dniowa blokada) → traktuj jako pełen booked (nie skos), albo opcjonalnie skos w kształcie klepsydry — w tym planie: pełen booked dla 1-dniowych.
3. Zamiast pojedynczego eventu `display: 'background'` z jednym kolorem, dodaj `classNames`:
- `yacht-day-booked` (środek)
- `yacht-day-booked-start` (pierwszy dzień)
- `yacht-day-booked-end` (ostatni dzień)
- analogicznie `yacht-day-blocked-*` dla statusu 'blocked'
4. Zachować istniejący `backgroundColor` jako fallback dla starszych przeglądarek; rzeczywiste tło renderować przez CSS (linear-gradient).
5. Przekazać kolor `availableBg` i `bookedBg` do CSS przez CSS custom properties ustawiane na wrapperze: `--yacht-available-bg`, `--yacht-booked-bg`.
- Set `$wrapper.css({'--yacht-available-bg': availableBg, '--yacht-booked-bg': bookedBg})`.
**CSS (calendar.scss + skompilowany calendar.css):**
1. Dodaj reguły:
```css
.yacht-day-booked-start .fc-bg-event,
.yacht-day-booked-start.fc-day {
background: linear-gradient(to right,
var(--yacht-available-bg) 0%,
var(--yacht-available-bg) 50%,
var(--yacht-booked-bg) 50%,
var(--yacht-booked-bg) 100%) !important;
}
.yacht-day-booked-end .fc-bg-event,
.yacht-day-booked-end.fc-day {
background: linear-gradient(to right,
var(--yacht-booked-bg) 0%,
var(--yacht-booked-bg) 50%,
var(--yacht-available-bg) 50%,
var(--yacht-available-bg) 100%) !important;
}
/* Analogicznie dla blocked-* */
```
2. Skos liniowy 50/50 — preferowany nad ukośnym (`to bottom right`), bo zachowuje czytelność daty i nie nakłada się na cyfrę.
3. Zaktualizować legendę widgetu o trzeci stan "Częściowo zajęty" (gradient sample) — tylko jeśli widget już renderuje legendę; jeśli nie, pominąć.
**Nie zmieniać:**
- logiki walidacji `selectAllow` / `select` (AC-6).
- struktury REST API (`/availability/{yacht_id}` zwraca dalej te same pola).
**Avoid:**
- Mutacji `event.display` — zostaw `'background'`, klasy CSS wystarczą.
- Inline styles na komórkach (FullCalendar je nadpisuje na re-render).
</action>
<verify>
1. `php -l wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php` — syntax OK.
2. Skompilować SCSS → CSS (lub edytować oba pliki ręcznie spójnie); `git diff calendar.css` pokazuje nowe selektory.
3. Manualnie: po deployu FTP otworzyć /rezerwacja-maja/, sprawdzić że dla istniejącej rezerwacji pierwszy/ostatni dzień ma skos. (CHECKPOINT human-verify w Task 3).
</verify>
<done>AC-1, AC-2, AC-3, AC-4, AC-6 spełnione.</done>
</task>
<task type="auto">
<name>Task 2: Zaktualizować domyślne kolory widgetu Elementor do palety strony</name>
<files>
wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
</files>
<action>
W `Calendar_Widget::register_controls()` (linie ~218245 oraz ~272274 dla `render()` i ~530532 dla `_content_template()`) zmienić wartości `'default'` na paletę dopasowaną do strony jachty3.pagedev.pl:
- `primary_color`: `#2271b1` → `#0a2a5e` (granat główny strony)
- `available_color`: `#35b56a` → `#f5f9ff` (bardzo jasny niebieski / prawie biały)
- `booked_color`: `#e53935` → `#d63638` (czerwień akcentu strony, taka sama jak CTA)
Jeśli paleta strony zostanie później doprecyzowana — wartości można dostroić w ramach UNIFY. Punkt wyjścia: granat #0a2a5e + czerwień #d63638 (zgodne z screenshotem strony).
Dodatkowo:
- W szablonie legendy (linie ~566570) zaktualizować `background-color` swatch'y do nowych defaultów (lub jeszcze lepiej: użyć CSS custom properties z wrappera, żeby legenda też reagowała na zmianę w Elementorze).
- Fallback w `calendar.js` (`normalizeColor`) — dodać stare zielone/czerwone `#35b56a` i `#e53935` jako "legacyColor", aby instancje wciąż używające tych wartości automatycznie dostały nowe domyślne. Dla istniejących instancji ze świadomie dobranymi kolorami (np. brand klienta) zachować jak jest — czyli **nie ruszać** instancji, gdzie wartość jest inna niż stary default.
**Avoid:**
- Migracji wartości w bazie danych (Elementor zapisuje per-widget; nie ruszamy istniejących).
- Łamania backward-compatibility dla widgetów bez ustawionego koloru (puste = nowy default).
</action>
<verify>
1. `php -l wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php`.
2. Po deployu: dodać nowy widget Yacht Calendar w Elementor → sprawdzić że suwaki pokazują nowe wartości HEX.
3. Otworzyć stronę z istniejącym widgetem — kolory niezmienione (jeśli zapisane w meta) lub odświeżone (jeśli legacy zielony/czerwony).
</verify>
<done>AC-5 spełnione.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Wizualne oznaczenie pół-dni i nowa paleta widgetu na środowisku produkcyjnym (po deployu FTP).
</what-built>
<how-to-verify>
1. Po wgraniu plików przez ftp-kr odświeżyć z hard-reload: https://jachty3.pagedev.pl/rezerwacja-maja/
2. Wybrać miesiąc, w którym istnieje rezerwacja (lub stworzyć testową).
3. Sprawdzić wizualnie:
- Pierwszy dzień rezerwacji: lewa połowa jasna, prawa czerwona.
- Środkowe dni: w pełni czerwone.
- Ostatni dzień: lewa czerwona, prawa jasna.
- Tło wolnych dni: jasno-niebieskie / białe (dopasowane do strony).
- Brak regresji w wyborze dat (selecton działa, walidacja overlap aktywna).
4. Sprawdzić w Elementorze: nowy widget Yacht Calendar → defaultowe kolory = granat/jasny/czerwień.
5. Mobile (DevTools 375px) — skos czytelny, data nie znika.
</how-to-verify>
<resume-signal>Wpisz "approved" aby kontynuować, lub opisz problemy do poprawienia (kolor, kierunek skosu, kontrast itp.)</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `wp-content/plugins/yacht-booking-system/api/class-rest-controller.php` — REST API surface bez zmian.
- `wp-content/plugins/yacht-booking-system/includes/class-availability.php` — schemat danych i metody bez zmian.
- Logika walidacji rezerwacji w `Rest_Controller::create_booking()` i `selectAllow` w JS — nie zmieniać polityki overlap.
- `wp_yacht_availability` — schemat tabeli niezmieniony.
- Backend admin (`admin/class-admin.php` itd.) — plan dotyczy tylko frontendu.
- Istniejące zapisane ustawienia kolorów w meta widgetów Elementora.
## SCOPE LIMITS
- Tylko frontend kalendarza (FullCalendar w widgecie + shortcode).
- Tylko CSS + JS detekcja krawędzi; brak nowych endpointów REST.
- Bez zmian w polityce check-in/check-out (godziny odbioru/zwrotu jako konfiguracja → ewentualnie osobny plan).
- Bez zmian w systemie email / templates.
- Bez tłumaczeń .po/.mo (osobny plan 09-04).
</boundaries>
<verification>
- [ ] `php -l` na zmodyfikowanych plikach PHP — bez błędów składni.
- [ ] Skompilowany `calendar.css` zsynchronizowany z `calendar.scss`.
- [ ] Brak nowych warningów w konsoli przeglądarki (DevTools) na /rezerwacja-maja/.
- [ ] FullCalendar nadal renderuje miesiąc/dni i refetch działa po zmianie jachtu.
- [ ] Wszystkie AC (AC-1..AC-6) zweryfikowane wizualnie w checkpoint human-verify.
</verification>
<success_criteria>
- Skos na pierwszym/ostatnim dniu każdej ciągłej blokady (booked + blocked).
- Środkowe dni renderują się w pełnym kolorze booked.
- 1-dniowe blokady renderują się jako pełen booked (decyzja: nie używamy klepsydry).
- Nowe defaulty kolorów w widgecie pasują wizualnie do palety strony (granat + czerwień).
- Istniejące widgety z customowymi kolorami nie tracą swoich ustawień.
- Klient potwierdza ("approved") w checkpoint human-verify.
</success_criteria>
<output>
Po zakończeniu: `.paul/phases/09-finalizacja/09-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,183 @@
---
phase: 09-finalizacja
plan: 01
subsystem: ui
tags: [fullcalendar, css, elementor, frontend, ux]
requires:
- phase: 04-frontend-kalendarz
provides: FullCalendar widget Elementor + REST availability endpoint
- phase: 03-system-dostepnosci
provides: wp_yacht_availability table z polem booking_id (bazą detekcji krawędzi)
provides:
- Wizualne oznaczenie pierwszego/ostatniego dnia rezerwacji jako pół-zajętego (gradient 45°)
- Detekcja krawędzi rezerwacji po stronie klienta (start/end/mid/single) na bazie sąsiedztwa booking_id+status
- Paleta widgetu Elementor dopasowana do brandu strony (granat #0e2036, czerwień #bc1834)
- CSS custom properties --yacht-available-bg / --yacht-booked-bg umożliwiające reużycie kolorów w gradientach i legendzie
affects:
- 09-02-settings (settings page może w przyszłości eksponować godziny check-in/out)
- przyszłe plany dotyczące UX kalendarza
tech-stack:
added: [CSS custom properties, FullCalendar --fc-border-color override]
patterns:
- "Edge detection przez sąsiedztwo: porównanie current vs (date-1, date+1) po booking_id i status"
- "Linear-gradient 50/50 z hard stop dla skosu 45° zamiast SVG/canvas"
key-files:
created:
- test-add-booking.php (helper testowy w roocie projektu — DO USUNIĘCIA po teście)
modified:
- wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js
- wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss
- wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css
- wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php
key-decisions:
- "Skos 45° z linear-gradient zamiast SVG/clip-path — prostszy CSS, działa we wszystkich nowoczesnych przeglądarkach"
- "Single-day blokada renderowana jako pełen booked (nie klepsydra) — uproszczenie wizualne"
- "Walidacja overlap (selectAllow/select) niezmieniona — polityka check-in/check-out poza zakresem"
- "Defaulty kolorów dopasowane do faktycznej palety strony (granat #0e2036, czerwień #bc1834) zamiast generycznych #0a2a5e/#d63638 z planu"
- "Cyfry dni #021526 z !important — przebicie globalnej reguły body a:not([href])"
patterns-established:
- "CSS custom properties ustawiane z JS na wrapperze pozwalają na reuse koloru w gradientach + legendzie bez duplikacji"
- "Krawędzie segmentu = porównanie 3 atrybutów (status, booking_id, sąsiad) — działa zarówno dla bookings jak i blocked z GCal/iCal"
duration: ~90min
started: 2026-05-06T08:00:00+02:00
completed: 2026-05-06T09:30:00+02:00
---
# Phase 9 Plan 01: UX/UI polish kalendarza — Summary
**Pierwszy/ostatni dzień rezerwacji renderuje się jako pół-zajęty trójkąt (skos 45°) z paletą strony (granat + czerwień); ramki dni i nagłówki kolumn ciemne, cyfry granatowe.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~90 min |
| Started | 2026-05-06T08:00:00+02:00 |
| Completed | 2026-05-06T09:30:00+02:00 |
| Tasks | 3 of 3 (2 auto + 1 checkpoint) |
| Files modified | 4 (plugin) + 1 utworzony (test helper) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Skos na pierwszym dniu rezerwacji | Pass | Po iteracji finalna orientacja: gradient 135° → top-left = wolny, bottom-right = zajęty |
| AC-2: Skos na ostatnim dniu rezerwacji | Pass | Gradient 135° → top-left = zajęty, bottom-right = wolny. Razem ze start tworzy parallelogram przez wiersz |
| AC-3: Detekcja ciągłości — różne booking_id | Pass | `sameSegment(a,b)` porównuje status + booking_id; różne `booking_id` przerywają segment |
| AC-4: Skos także dla blokad GCal/iCal | Pass | Klasy `yacht-day-blocked-start/end` reagują na ten sam gradient — jeden zestaw reguł |
| AC-5: Nowa paleta domyślna widgetu | Pass | primary #0e2036, available #f5f9ff, booked #bc1834 (zamiast generycznych z planu — wzięto faktyczną paletę z SCSS strony) |
| AC-6: Walidacja wyboru dat niezmieniona | Pass | `selectAllow` i `select` callback bez zmian, overlap dalej blokuje |
## Accomplishments
- Skos 45° pierwszego/ostatniego dnia rezerwacji (gradient 135° + 135°), spójny dla bookings i blokad GCal/iCal
- Detekcja krawędzi po stronie klienta — bez zmian w REST API ani schemacie DB
- Paleta widgetu dopasowana do brandu strony (granat + czerwień) z fallbackiem dla legacy zielony/czerwony
- Ciemne ramki kalendarza przez `--fc-border-color` (oficjalna zmienna FullCalendar v6) + override dla 7 selektorów
- Cyfry dni granatowe `#021526`, past `#6c757d` zamiast białych
- Legenda rozszerzona o swatch „Dzień odbioru / zwrotu" z gradientem 135°
- Helper `test-add-booking.php` z token-protected akcjami add/list/remove dla łatwego smoke testu
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `wp-content/plugins/yacht-booking-system/frontend/assets/js/calendar.js` | Modified (+58/-3) | Edge detection w `events` callback; CSS custom properties na wrapperze; nowe defaulty `#f5f9ff`/`#bc1834` |
| `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.scss` | Modified (+61/-7) | Gradienty 135° dla start/end; ciemne ramki przez --fc-border-color + override; nagłówki kolumn granatowe; cyfry dni #021526 |
| `wp-content/plugins/yacht-booking-system/frontend/assets/css/calendar.css` | Modified (~) | Skompilowane reguły zsynchronizowane z SCSS |
| `wp-content/plugins/yacht-booking-system/frontend/class-calendar-widget.php` | Modified (+15/-7) | Defaulty kolorów Elementora; legenda rozszerzona o swatch gradientu |
| `test-add-booking.php` | Created | Token-protected helper FTP do tworzenia/listowania/usuwania testowych rezerwacji |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Skos przez `linear-gradient(135deg, A 50%, B 50%)` zamiast SVG/clip-path | Najprostsze CSS, brak dodatkowych assetów, pełne wsparcie w nowoczesnych przeglądarkach | Łatwa modyfikacja kątów / kierunków bez zmian w JS |
| Paleta `#0e2036`/`#bc1834` zamiast `#0a2a5e`/`#d63638` z planu | Po inspekcji `calendar.scss` znaleziono faktyczne kolory już używane na stronie (przyciski, switcher, instructions) | Spójność wizualna z resztą widgetu — bez nowych kolorów do utrzymania |
| Iteracja kierunku skosu pierwszego dnia (45° → 135°) | Klient zgłosił błędny kierunek po pierwszym deployu; flip → parallelogram przez wiersz | Wymagała 2 deployów, ale teraz oba dni mają tę samą oś diagonalną |
| Cyfry dni z `!important` na `.fc-daygrid-day-number` | Globalna reguła `body a:not([href]):not([tabindex])` na końcu pliku ustawiała `color: #fff` na linkach FullCalendar | Bez !important globalna reguła wygrywała specyficznością |
| Single-day blokada = pełen booked (klasa `*-single`, brak gradientu) | Skos klepsydry byłby wizualnie mylący dla 1-dniowej blokady | Klasa `*-single` zarezerwowana — łatwa do wykorzystania w przyszłości |
| `test-add-booking.php` w roocie z tokenem | Zgodne z konwencją projektu (`test-*.php` per CLAUDE.md); brak DB CLI; token chroni przed nieautoryzowanym użyciem | Plik DO USUNIĘCIA po testach — nie commitować na produkcję |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 3 | Essential UX fixes wymagane przez klienta podczas verify |
| Scope additions | 1 | test-add-booking.php helper — nie był w planie ale niezbędny do weryfikacji |
| Deferred | 0 | — |
### Auto-fixed Issues
**1. [UX] Ramki dni w kalendarzu były jasne mimo override w pierwszej iteracji**
- **Found during:** Checkpoint human-verify (iteracja 2)
- **Issue:** Pierwotny override `border-color: #0e2036 !important` na 3 selektorach nie działał — FullCalendar v6 używa `--fc-border-color` na wielu warstwach (scrollgrid section, daygrid-day, theme-standard td/th)
- **Fix:** Ustawiłem `--fc-border-color: #0e2036` na `.yacht-calendar` i `.yacht-calendar .fc` plus override na 7 selektorach (td/th/scrollgrid/scrollgrid-section/daygrid-day/col-header-cell)
- **Files:** calendar.scss, calendar.css
- **Verification:** Klient potwierdził „Jest ok" po deployu
**2. [UX] Skos pionowy zamiast 45°**
- **Found during:** Checkpoint human-verify (iteracja 2)
- **Issue:** Pierwotny gradient `to right` dawał pionowy podział 50/50 — nieczytelny estetycznie i niezgodny z image013.png
- **Fix:** Zmiana na `linear-gradient(45deg, ...)` (start) i `linear-gradient(135deg, ...)` (end), potem korekta start na `135deg` po feedbacku
- **Files:** calendar.scss, calendar.css, class-calendar-widget.php (legenda)
- **Verification:** „Jest ok" po finalnej korekcie kierunku
**3. [UX] Cyfry dni białe (nieczytelne na jasnym tle)**
- **Found during:** Checkpoint human-verify (iteracja 4)
- **Issue:** Globalna reguła `body a:not([href]):not([tabindex]) { color: #fff }` (linia 672 SCSS) pokrywała linki cyfr FullCalendar
- **Fix:** `.yacht-calendar .fc-daygrid-day-number { color: #021526 !important; font-weight: 600 }`. Past days `#6c757d`
- **Files:** calendar.scss, calendar.css
- **Verification:** Klient potwierdził „Jest ok"
### Scope Additions
**1. test-add-booking.php — token-protected helper**
- **Reason:** Klient chciał zobaczyć podzielone dni; brak istniejącej rezerwacji do testów; brak dostępu DB CLI z tej maszyny
- **Implementation:** Skrypt PHP w roocie z tokenem `YACHT_TEST_2026`, akcje add/list/remove, marker meta `_yacht_test_booking_marker = PAUL_TEST_09_01`
- **Cleanup:** Plik DO USUNIĘCIA z FTP po zakończeniu testów (jest w `.gitignore`? — nie, świadomie zostawiamy w repo lokalnym aby user mógł użyć ponownie; produkcyjnie usunąć z FTP)
### Deferred Items
Brak.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Pierwsza iteracja gradientu pionowa zamiast skośnej | Klient feedback → kąt 45°/135° |
| Kierunek skosu pierwszego dnia odwrotny niż oczekiwany | Flip 45° → 135° na start |
| Cyfry dni białe | !important override na `.fc-daygrid-day-number` |
| Ramki dalej jasne po pierwszej próbie | Dodanie `--fc-border-color` (zmienna FullCalendar v6) plus szerszy zestaw selektorów |
## Skill Audit
Brak `.paul/SPECIAL-FLOWS.md` w projekcie — pominięto.
## Next Phase Readiness
**Ready:**
- Phase 9 / Plan 01 zamknięty, kalendarz UX/UI w stanie produkcyjnym
- Paleta widgetu znormalizowana, można używać CSS variables w przyszłych iteracjach
- Helper `test-add-booking.php` dostępny do dalszych smoke testów
**Concerns:**
- Walidacja overlap dalej blokuje rezerwację rozpoczynającą się w dniu zwrotu — wizualnie skos sugeruje że to dozwolone. Decyzja biznesowa: czy klient chce wprowadzić politykę check-in/check-out z godzinami (osobny plan)
- `test-add-booking.php` powinien zostać USUNIĘTY z FTP po testach (zostaje w repo dla wygody)
**Blockers:**
- Brak. Faza 9 może kontynuować planem 09-02 (Settings Page)
---
*Phase: 09-finalizacja, Plan: 01*
*Completed: 2026-05-06*