feat(setup): post-link redirects to SPA so first-setup matches live UI
CI / test (push) Has been cancelled

Twig configure page replaced with a redirect: SetupController's index,
register, login, and the legacy /configure route all post-link redirect
to /?setup=<deviceId> for unconfigured devices. The SPA's HomeView
auto-opens its existing settings sheet for that id, with the same
controls everyone uses for live edits — themed to the user's choice,
pre-populated from the device record.

Fixes Matt's report:
  - "every 6 hours" lost on save: the configure form posted
    rotation_interval_hours but the controller read
    rotation_interval_minutes, so the value silently defaulted to
    1440 every time. Now the SPA's PATCH flow handles it correctly.
  - "old settings still there in live settings": SPA settings sheet
    pre-populates from the device's current state via onEdit.
  - "uniqueness window in setup but not live settings": removed
    from the (now-deleted) Twig form; both surfaces are consistent.
  - "color scheme didn't match account": SPA respects the user's
    theme natively (data-theme on <html>), so the first-setup screen
    looks like the rest of the app.

Also adds a "Sign out of pictureFrame" link at the bottom of the
per-frame settings sheet (the existing /settings tab still has the
primary one). Easy escape hatch from a deeply-nested settings flow.

Tests:
  - SetupControllerTest: S-03/04/05/06/08 updated for new redirect
    targets, S-CLAIM-03 updated.
  - HomeView.test.ts: useRoute now mockable per-test, two new cases
    pinning the ?setup=<id> auto-open and its absence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 18:51:31 -04:00
parent ff1ae79824
commit 08d0968af0
14 changed files with 139 additions and 95 deletions
+35 -3
View File
@@ -231,6 +231,8 @@
class="home-view__remove"
@click="removeConfirmOpen = true"
>Remove this frame</button>
<a href="/logout" class="home-view__logout">Sign out of pictureFrame</a>
</BaseBottomSheet>
<Teleport to="body">
@@ -279,7 +281,7 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import { useDevicesStore } from '@/stores/devices'
import { useUploadStore } from '@/stores/upload'
import { useDeviceMercure } from '@/composables/useDeviceMercure'
@@ -402,6 +404,7 @@ import OrientationPicker from '@/components/OrientationPicker.vue'
import PullToRefresh from '@/components/PullToRefresh.vue'
const router = useRouter()
const route = useRoute()
const devicesStore = useDevicesStore()
const uploadStore = useUploadStore()
@@ -409,9 +412,22 @@ const uploadStore = useUploadStore()
// (and on PATCH/lock/unlock); the composable splats it into the store.
useDeviceMercure()
onMounted(() => {
devicesStore.fetchDevices()
onMounted(async () => {
await devicesStore.fetchDevices()
document.addEventListener('visibilitychange', onVisibility)
// First-time setup landing: SetupController redirects new users to
// /?setup=<deviceId> after register/login, instead of a separate
// Twig configure page. Auto-open the settings sheet for that device
// so the user sees the same rich UI everyone else uses for live edits
// — pre-populated, themed, with no duplicated form to maintain.
const setupId = Number(route.query.setup)
if (setupId) {
onEdit(setupId)
// Clean the query param off the URL so a refresh doesn't keep
// re-opening the sheet, but keep the user on the home view.
router.replace({ query: { ...route.query, setup: undefined } })
}
})
onUnmounted(() => {
@@ -1150,5 +1166,21 @@ async function saveSettings() {
border-color: var(--color-danger, #c0392b);
color: #fff;
}
&__logout {
display: block;
margin-top: var(--space-3);
text-align: center;
font-size: var(--text-xs);
color: var(--color-text-muted);
text-decoration: none;
padding: var(--space-2);
&:hover, &:focus-visible {
color: var(--color-text);
text-decoration: underline;
outline: none;
}
}
}
</style>