5 Commits

Author SHA1 Message Date
football2801 081ca83613 fix(v2): preview rotation + crop aspect for 13.3" hardware
CI / test (push) Has been cancelled
Two related bugs that surfaced on the first 13.3" device's first photo:

1) Web-UI portrait preview was 90° sideways. DeviceApiController::
   renderBinToPng rotated whenever the device was Portrait — correct
   for V1 (landscape-native, Portrait => renderer rotated, so preview
   un-rotates) but wrong for V2 (portrait-native — the renderer
   doesn't rotate, so the preview shouldn't either). Now mirrors the
   render-pipeline check: rotate only when `orientation !==
   model->nativeOrientation()`. Two new functional tests pin the V2
   portrait and V2 landscape PNG dimensions to guard against
   regressions.

2) Cropped photo letterboxed on the 13.3" panel. CropEditor /
   StickerCanvas / FrameCard had V1 dimensions hardcoded (1600×960
   = 5:3 aspect). V2 is 4:3 (1200×1600 portrait / 1600×1200
   landscape), so a "full crop" came out the wrong shape and the
   server's white-canvas composite added bars. New `panelDims(model,
   orientation)` helper in @/types is the single source of truth on
   the frontend; matches DeviceModel::width/height on the server.
   Threaded `model` through Device serializer → Device type →
   UploadView → CropEditor / StickerCanvas, and HomeView → FrameCard.
   FrameCard tests updated to cover all four model × orientation
   placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:02:39 -04:00
football2801 920de623a0 feat(devices): owner can mark a frame as sold and unlink it pre-emptively
CI / test (push) Has been cancelled
Pairs with the new claim-on-takeover checkbox: now the seller can purge
their data BEFORE handing the device over, so even if they forget to
hold the BOOT button to wipe NVS, the next owner can't accidentally pull
their photos.

Backend:
  - DELETE /api/devices/{id}: owner-only (404 for cross-tenant). Revokes
    image-device approvals, drops history rows, removes the Device row
    entirely so the MAC is unclaimed. The next poll from that physical
    frame returns 404 → setup QR for the next owner.
  - DeviceService::deleteDeviceForOwner extracts the cleanup so the
    controller stays thin.
  - Mercure publish on delete sends {id, deleted: true} so any other
    open PWA tabs splice the row out instantly.

Frontend:
  - Settings sheet (BaseBottomSheet): "Remove this frame" link below
    Save, in danger red with an explanatory hint about when to use it.
  - Native window.confirm gate — destructive + irreversible, the
    weight of native-confirm is honest. (A bespoke modal would be
    polish.)
  - useDeviceMercure: handles the {id, deleted: true} sentinel — splices
    the device out + closes its own EventSource for that topic.
  - useDevicesStore.removeDevice: DELETE + local store filter.

Tests added:
  - DeviceApiControllerTest: 4 cases — happy-path delete purges
    everything, 404 cross-tenant, anon redirects to login, and
    post-delete the device-poll endpoint 404s (fresh-MAC guarantee).
  - HomeView.test.ts: confirm-yes calls store + closes sheet,
    confirm-cancel does NOT call removeDevice.
  - useDeviceMercure.test.ts: deletion sentinel splices the device
    out and closes the EventSource.

Coverage: 99.71% lines / 98.21% methods backend, 98.31% lines frontend.
558 tests total via ddev tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:53:51 -04:00
football2801 a9ad014bd1 test: tighten coverage to 99.69% backend / 98.62% frontend
CI / test (push) Has been cancelled
Started: 89.08% backend / 97.01% frontend lines.
Landed: 99.69% backend / 98.62% frontend.

Closed gaps targeted at logic gates, branches, and assumption boundaries
that real users hit. Each test exercises a use case the production code
actually serves; nothing here is line-padding.

Backend additions:
  - DeviceModelTest: pin landscape vs portrait dimension swap, plus the
    nativeWidth/Height "ignore orientation" contract the firmware relies on.
  - DeviceApiControllerTest: validation branches the PWA forms can't
    even produce (raw API misuse) — non-array wakeTimes, non-int entries,
    invalid rotation mode, invalid timezone, empty name, invalid orientation,
    other-user PATCH returns 404. Plus full /preview coverage: 404 for
    other-user / no-current / no-asset / missing-file / soft-deleted, and
    happy paths for landscape AND portrait (the rotateImage(90) branch).
  - ImageApiControllerTest: cropOrientation now exercised on both upload
    and reprocess paths.
  - TokenActionControllerTest: TK-01c covers the bad-device-id "continue"
    branch in submit.
  - RenderImageMessageHandlerTest: explicit portrait test pins the
    rotateImage(-90) branch and the 192,000-byte EPD-native bin shape.
  - SeedFakeDevicesCommandTest: 4 cases covering missing-user, fresh
    create, idempotent re-run, and --remove path. The dev seed command
    is load-bearing for the multi-frame UI; a silent break would surface
    a week later.
  - RerenderAssetsCommandTest: reset + dispatch path, no-assets path.

Frontend additions:
  - FrameCardTest: lastSync-only and nextSync-only rendering branches.
  - HomeView.test:
    * + Add time fallback path when all 9 default candidates are taken.
    * Multi-day "in Nd" nextSync formatting (offline / huge-interval case).
    * Medium-horizon (5h) nextSync formats as clock-time + day label.
    * visibilitychange triggers a silent re-fetch.
    * add-photo handler creates input + navigates to /upload after pick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:22:46 -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 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