Commit Graph

10 Commits

Author SHA1 Message Date
football2801 2be153a103 feat(help): public /help setup-and-troubleshooting page
CI / test (push) Has been cancelled
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>
2026-05-09 11:06:42 -04:00
football2801 2a8bf3895f chore(dev): DDEV setup so the test suite actually runs
CI / test (push) Has been cancelled
Mirrors aqua-iq's pattern but adapted for pictureFrame's stack:
postgres 16, php 8.4, node 22, imagick + pcov via apt extras,
Mercure hub at https://pictureframe.ddev.site/.well-known/mercure,
and four custom commands — `ddev tests`, `ddev coverage`,
`ddev frontend` (vite HMR), `ddev worker`.

Also restores dev deps (DAMA, Doctrine fixtures, symfony/uid) that
got dropped during earlier composer reshuffles, and adds a separate
`db_test` connection in .env.test so DAMA's transactional isolation
doesn't share state with whatever dev is mid-experiment with.

Two test fixes the new env exposed:
  - RotationServiceTest::test_prioritize_never_shown_falls_through_when_all_shown
    needed uniquenessWindow=2 so the recent-window filter wipes the
    set and the fallback restores the full pool — otherwise window=1
    excluded the most-recently-served image and the assertion drifted.
  - DeviceImageControllerTest::test_locked_image_served_without_rotation_advance
    was asserting currentImage stays null on a lock poll, but the
    controller intentionally sets currentImage on the lock path so
    Home reflects the live frame state. Now asserts both the
    currentImage update AND that no DeviceImageHistory row was
    written (the actual rotation-bypass guarantee).

Backend coverage (full suite via `ddev coverage`): 89.08% lines /
92.24% methods / 74.36% classes — the first real number we've had.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:56:36 -04:00
football2801 ba9625d45d feat(home): live updates via Mercure — server pushes device state to the PWA
CI / test (push) Has been cancelled
Subscribe per-device with a Symfony Mercure hub: server publishes a fresh
device payload after every poll (200/304/204), every PATCH, and every
lock/unlock. The frontend opens one EventSource per device topic and
splats inbound JSON straight into the devices store — same shape as
GET /api/devices, so no envelope handling.

Topic: https://pictureframe.edholm.me/devices/{id}

Stack mirrors aqua-iq:
- symfony/mercure-bundle + config/packages/mercure.yaml
- App\Service\MercurePublisher (errors swallowed + logged; a flaky hub
  must not break a poll response)
- App\Service\DeviceSerializer extracted as the single source of truth
  for the wire shape (REST + Mercure share it)
- Frontend useDeviceMercure() composable: opens/closes EventSources to
  match the device list reactively, reconnects on hub-side closes
- SpaController exposes MERCURE_PUBLIC_URL via window.__PF_MERCURE_URL__

Production compose adds a dunglas/mercure container with Traefik labels
for pictureframe.edholm.me/.well-known/mercure (handled separately on
the host since the file isn't in this repo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:20:21 -04:00
football2801 b0c27a9332 fix: gate dama doctrine test bundle config under when@test
CI / test (push) Has been cancelled
Bundle was registered for test env only but its YAML config loaded
unconditionally, which broke `bin/console cache:clear` in dev/prod
when the bundle is enabled but the env doesn't load it. The file now
only contributes to the test container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:46:02 -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 604d37d102 fix: trust Traefik reverse proxy so Symfony generates https:// redirects
Traefik terminates TLS and forwards X-Forwarded-Proto: https to Nginx,
which forwards it to PHP-FPM. Without trusted_proxies, Symfony ignored
this header and generated http:// redirect URLs after login/register,
causing session cookie loss on mobile (Secure cookie not sent over HTTP).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:10:44 -04:00
football2801 3c1d5f0eae feat(story-1.4): user login with remember_me, inline error, logout
- Login Twig template: styled to match register page; inline "Incorrect email or
  password" on both fields (no email-existence disclosure); aria-invalid on error
- security.yaml: always_remember_me: true — REMEMBERME cookie set on every login
- Logout: /logout → session invalidated → 302 /login (Symfony firewall handles it)

Verified: correct creds → 302 / + REMEMBERME cookie; wrong creds → 302 /login +
          inline error on re-render; logout → 302 /login; GET / after logout → 302 /login

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:36:39 -04:00
football2801 694843bdf0 feat(story-1.3): user registration with auto-login and inline validation
- RegistrationFormType: email + plainPassword, NotBlank/Email/Length(min=8) constraints
- SecurityController: register action hashes password, persists user, auto-logs in via Security::login()
- User entity: UniqueEntity constraint — "An account with this email already exists"
- Register Twig template: inline errors per field (role=alert), blur-validation JS
  (client fires on blur not keystroke; server-error flag prevents blur clobbering server messages)
- csrf.yaml: switched from stateless UX-dependent tokens to standard session CSRF
  (stateless token IDs require Stimulus JS to inject the real value — we removed Stimulus)

Verified: happy path → 302 + auto-login; duplicate email → 422 + inline error;
          short password → 422 + inline error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:25:42 -04:00
football2801 a55b3bd187 feat(story-1.2): Vue 3 SPA scaffold, base component library, User entity, SpaController
Frontend:
- Vue 3 + Vite + TypeScript strict in frontend/; builds to public/build/
- Vue Router (hash-history, requiresAuth guard → /login) and Pinia
- Global SCSS design tokens with 6 full themes (Warm Craft, Playful Pop, Sage & Cream, Dusty Mauve, Ocean Dusk, Honey & Slate)
- Base components: BaseButton (5 variants), BaseInput (floating label, error state),
  BaseBottomSheet (slide-up, focus trap, tap-outside dismiss), BaseCard, BaseChip,
  BaseToast (2.5s auto-dismiss, aria-live polite), BottomNav (4 tabs, hides at 960px)
- Type stubs for all API shapes: User, Device, Image, StickerLayer, RenderedAsset, Token

Backend:
- SpaController catch-all serves public/build/index.html; excludes api/setup/token/login/register
- User entity (email+password+roles+theme); UserRepository with PasswordUpgrader
- SecurityController with /login, /logout, /register stubs; Twig login form
- security.yaml: form_login firewall, remember_me, role_hierarchy, access_control
- Migration: create user table

Verified: npm run build succeeds, GET / → 302 /login (unauthenticated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 23:21:29 -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