Commit Graph

7 Commits

Author SHA1 Message Date
football2801 5b3e2e47d7 fix(rotation): gate poll-driven advance() behind isDue() check
CI / test (push) Has been cancelled
Symptom: with wakeTimes=[4 AM, 9 PM, 9:15 PM], the frame rotated to a
fresh photo at 10:14 AM when the device was reconnected and polled.
The wakeTimes only governed *when* the device polled (via X-Interval-Ms);
they didn't gate whether the server picked a new image when it did.
Power-on or button-press polls would always rotate.

Fix: move the existing isDue() logic from AdvanceRotationMessageHandler
into RotationService as a public method, and gate
DeviceImageController::image so off-schedule polls return the device's
current image (which 304s when X-Current-Image-Id matches) rather than
calling advance(). The scheduler-driven handler still uses the same
isDue — both code paths now share one source of truth.

Tests:
  - DeviceImageControllerTest: new test asserting an off-schedule poll
    returns 304 without rotating; existing wakeTimes tests reworked to
    use slot lists that always have a past slot regardless of run time.
  - AdvanceRotationMessageHandlerTest: existing AR-04 through AR-07
    keep covering isDue's semantics — they now go through the service.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:14:32 -04:00
football2801 bf9d4ebc58 test: close coverage gaps from the recent rotation + Mercure work
CI / test (push) Has been cancelled
Frontend (90.15→95.37 stmts / 91.83→97.01 lines):
  - useDeviceMercure: full composable test suite via a fake EventSource —
    open/merge/ignore-stale/parse-error/reconnect/dynamic-add/remove/
    no-op-when-unconfigured/cleanup-on-unmount.
  - HomeView: cover onTimePart's AM/PM and minute branches plus the
    nextPollExpectedAt-null fallback paths in the next-update preview.

Backend (no instrumentation before; pcov was already in the image,
just needed a <coverage> block in phpunit.dist.xml):
  - RotationService: one test per mode (NewestUpload, Random,
    LeastRecentlyShown), one for never-shown sorting first under LRS,
    and two for prioritizeNeverShown — narrows when never-shown exists,
    falls through to mode otherwise.
  - DeviceSerializer: contract test on the wire shape (REST + Mercure
    use the same serializer; silent rename here would break live updates
    instantly).
  - MercurePublisher: topic format + JSON encoding + the swallow-
    exceptions guarantee (a flaky hub must not break poll responses).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:25:25 -04:00
football2801 d11ddff912 feat(device): replace daily wakeHour with multi-time wakeTimes (minutes)
CI / test (push) Has been cancelled
Frame settings now offer two update-frequency modes: "at specific times" or
"every X minutes". Times are stored as an int[] of minutes-since-midnight,
allowing multiple slots per day at minute granularity. Backend computes the
earliest upcoming slot for X-Interval-Ms and uses the most-recent-past slot
as the rotation-due boundary. PWA settings sheet has hour/minute/AM-PM
dropdowns with + Add / trash, a live "next update" preview, and a note
that changes only take effect at the device's next sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:32:58 -04:00
football2801 b700a4a018 fix: include rendered_at in 304 cache check so re-renders invalidate
CI / test (push) Has been cancelled
After re-cropping an image, the renderer regenerates the .bin and
advances the asset's rendered_at, but the device's 304 short-circuit
still matched on (image_id, orientation) only — so the device kept
serving the old upside-down/stale bytes from its local cache despite
the server having freshly-rendered correct ones.

Adds device.current_rendered_at, populated whenever a 200 response is
served, and tightens the 304 condition to require all three (image id,
orientation, rendered_at) to match. The asset lookup now happens before
the 304 check so its rendered_at is in scope for the comparison.

No firmware change — this is server-side cache logic. Existing devices
get null current_rendered_at after the migration; their next poll falls
through 304 and re-fetches once, then the cache is in sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:16:48 -04:00
football2801 c387260ee7 fix: include orientation in device 304 cache check
CI / test (push) Has been cancelled
The 304 short-circuit at DeviceImageController only compared image IDs,
so flipping a device between landscape and portrait would not invalidate
the cache: the device kept showing the previously-rendered .bin even
after the user changed orientation in the webapp.

Now the device row tracks currentImageOrientation — set whenever a 200
binary response is sent — and the 304 path requires both image id AND
current orientation to match the device's stored orientation. An
orientation flip naturally falls through to the 200 path on the next
poll, the freshly-rendered portrait .bin is delivered, and the device
redraws.

No firmware change: the existing X-Current-Image-Id header from the
device is sufficient. Existing devices migrate cleanly — null
currentImageOrientation just forces one full re-send on first post-
migration poll, which is harmless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:21:15 -04:00
football2801 12245759ac chore: stage all in-progress work before repo split
CI / test (push) Has been cancelled
Web app: new entities (Image, RenderedAsset, SharedImage, Token,
DeviceImageHistory), enums, repositories, controllers, message handlers,
migrations, tests, frontend upload/library/sticker UI, Vue components.

Firmware: EPD background screen binaries + gen scripts, setup_bg header.

Infra: ddev config, test bundle, gitignore coverage dir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:11:31 -04:00
football2801 378b0b858b feat(story-1.1): scaffold Symfony 7.4 LTS app with DDEV, Messenger, Scheduler
- DDEV config: PHP 8.4, PostgreSQL 16, nginx-fpm, Imagick via webimage_extra_packages
- Symfony 7.4 LTS skeleton + webapp pack scaffolded via Composer
- Removed AssetMapper, Stimulus, UX-Turbo (replaced by Vue 3 SPA per architecture)
- Added symfony/messenger + symfony/scheduler (Doctrine transport)
- Gitea CI workflow: PHP 8.4 container + PostgreSQL 16 service, runs phpunit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 22:57:09 -04:00