Closes#2558.
Enables the Arduino WebServer's built-in CORS support
(`enableCORS(true)`), which adds
`Access-Control-Allow-Origin/Methods/Headers: *` to every response, and
answers preflight `OPTIONS` requests with `204` in `handleNotFound()` —
routes are registered per-method, so OPTIONS always lands there. The
AP-mode captive-portal redirect is untouched (the OPTIONS check runs
before it, and browsers don't send preflights for captive-portal
probes).
This lets web-based clients and PWAs served from other origins call the
JSON API (`/api/status`, `/api/files`, `/api/settings`, ...) directly
from the browser.
Overhead is three static response headers; no behavior change for the
built-in web UI.
Note: not yet tested on hardware.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: metoli <metoli@metoli-Mac-mini.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Make KOReader progress sync a one-click flow for common cases while
keeping the existing manual mode available.
### Changes
- Add a **Sync Behavior** setting:
- **Smart sync** for new configurations
- **Ask every time** for manual control
- Preserve **Ask every time** when migrating an existing credential file
that predates this setting.
- In Smart mode:
- upload local progress when no remote record exists;
- show a short confirmation when already synced;
- upload when local progress is further ahead;
- apply remote progress when remote progress is further ahead.
- Probe both KOReader document-matching hashes before making the Smart
decision, while keeping uploads on the user's configured matching
method.
- Auto-return after successful Smart terminal states, with Back/Confirm
still available.
- Persist the setting alongside the current credential, matching-method,
and send-metadata fields.
- Document both behaviors in the user guide.
## Additional context
This pairs well with #2189 (smart Wi-Fi auto-connect), but does not
depend on it. It does not add background Wi-Fi or passive network
detection; sync is still triggered by the user from the reader menu.
Smart sync uses the furthest progress because CrossPoint does not
currently persist local progress timestamps. Users who prefer explicit
conflict resolution can select **Ask every time**.
## Verification
- `git diff --check`
- `platformio check --fail-on-defect low --fail-on-defect medium
--fail-on-defect high` — no defects
- `platformio run -e default` — firmware build successful
- The original implementation was manually smoke-tested on an X4 device.
---
### AI Usage
Did you use AI tools to help write this code? _**YES**_
AI tools were used to inspect the codebase, draft and review the
implementation, resolve the rebase against current `develop`, and
prepare the PR text. The resulting firmware was built locally.
---------
Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
Co-authored-by: Alexander Hoffer <contact@alexanderhoffer.com>
Changes all sleep screen display refreshes and boot screens from
FULL_REFRESH to HALF_REFRESH to match OEM firmware behavior. The stock
firmware uses a single-pass 0xD7 waveform for sleep screens, not the
multi-flash GC waveform (0xF7) that FULL_REFRESH triggers. For grayscale
mode, HALF_REFRESH is required because the gray nudge LUT is calibrated
against the pixel charge state left by the single-pass waveform; using
FULL_REFRESH causes blotchy noise in gray areas. Also adds X3 wb_gc
passive byte (the one-byte 0x54 to 0x00, matching stock's passive WB
table in the gc nudge bank): Every X3 grayscale render, reader AA,
covers, and the sleep-cover nudge, gets it.