Mercure topic identifiers updated in lockstep across PHP publisher + TS
subscriber (and their tests). Help-page setup instructions now point to
wevisto.com. Traefik already serves both hosts; this aligns the in-app
references with the public brand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- crop: invert overlay shading; the destination-out trick on a
semi-transparent fill was leaving the *inside* of the crop more
transparent than the outside, so the keep-area read as darker
than the discard-area. Replace with 4 explicit dim-strips.
- stickers: floating trash handle now glues to the selected
sticker's top-right corner instead of an off-canvas X that
testers missed.
- stickers: replace the curated grid with an emoji-keyboard
picker — recently-used row, custom-sprite row (santa hat as
inline SVG), then an input that pops the OS emoji keyboard.
Recents persist in localStorage; legacy stickers fall back to
the old STICKERS table.
- pwa-install modal: drop "browser chrome" — beta tester read it
as the literal Chrome browser.
- /setup landing page: tighten "Set up your frame" copy.
Anchor for the manual QR baked into the firmware Step 1/2 screen
(pictureframe-firmware e089911). One self-contained Twig page,
PUBLIC_ACCESS in security.yaml, /help excluded from the SPA catch-all
regex so it doesn't get swallowed.
Scope is deliberately tight: first-time setup screen by screen, the
captive-portal-didn't-open fallback (manual nav to 192.168.4.1 plus
the iOS lock-screen-scan caveat), the connection-failed retry path,
how to wipe the frame for a new account, photo-not-appearing
diagnosis, and what the yellow/red status borders mean. Anything
beyond that goes in the SPA proper, not here.
No frontend rebuild needed — pure server-rendered Twig + inline CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use case: old owner sells the device to a friend. Friend holds the BOOT
button to wipe NVS, joins the device's AP, sets new WiFi. The old
owner's account is still bound to the MAC server-side, so without
explicit consent the friend would silently take over (or, worse, the
old owner's photos would keep displaying until claim).
Flow now:
- GET /setup/{mac} detects MAC bound to anyone and renders a
"Claim this frame as my own" checkbox + a banner explaining what
the takeover wipes. Both register and login panels carry the
checkbox; submitting either form without it bounces back through
the index with a session-flashed error.
- DeviceService::linkToUser now requires allowClaim=true to
transfer ownership. Without it, throws DeviceClaimRequiredException
that the controller catches and turns into the bounce-with-error.
- On a successful claim, the takeover wipes:
* old image-device approvals
* device_image_history rows for the device
* name, wakeTimes, currentImage*, lockedImage, nextPollExpectedAt
so the new owner starts from a fresh slate, not inheriting the
seller's "Living Room / 4:30 AM" preset.
- Already-logged-in user visiting /setup/{mac} for someone else's
device falls through to the form (instead of silently transferring
on page load) so the checkbox is the only path.
Test matrix:
- SetupControllerTest: 5 new functional cases — checkbox renders for
bound MACs, register/login without checkbox bounce + retain old
ownership, register WITH checkbox transfers + purges, logged-in
other-user falls through to form.
- DeviceServiceTest: 3 new unit cases — throw without consent,
isClaimedByAnotherUser true/false matrix, takeover resets device
state.
Coverage: 99.70% lines / 98.19% methods backend, 333 frontend tests
green via ddev tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace orientation <select> dropdown on setup/configure with the same
visual two-button picker used in the SPA (SVG frame diagrams, ribbon
indicator, active highlight). Hidden input carries the value on submit.
Firmware: normal_operation() now calls show_setup_qr(mac) on 404 instead
of epd_fill(COLOR_RED) — device shows scannable QR with its own MAC when
not yet registered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RepeatedType renders as a wrapper div when called as a single widget,
producing unstyled inputs with no mismatch error handling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Collapse orientation to landscape/portrait (ribbon left = portrait standard)
- Add OrientationPicker component and wire settings sheet in HomeView
- Add password confirmation field to registration form (RepeatedType)
- Build frontend SPA to public/build/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>