Point the panel link at the installation, not at the browser's origin

Both links out of the dashboard editor were built root-relative, so a portal
serving the app under `/i/{id}` got a URL to itself: the hub has no route
there and answers a bare 404. That is what a device link and "open what a
wall panel sees" both landed on.

They want different answers. The view link is for the person already looking,
so it takes the router's basepath — `appPath` in `lib/portal` is the same
prefix the router applies to every `Link`, for the places that step outside
it. The device link is for a screen, which cannot go through the portal at
all: the shell is served only to a portal session, and the credential that
page carries is the portal's rather than the panel's. So the server now says
where it answers, and `FRONTEND_HOST` is that answer — the same setting the
password-reset links already use.

Also fixes the panel branch in the query error handler, which compared a raw
pathname and so never fired under a portal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHpLJHozysQXjsxAyU1WHj
This commit is contained in:
2026-08-20 17:24:20 +02:00
co-authored by Claude Opus 5
parent ffae24c16b
commit ce77262f81
10 changed files with 119 additions and 19 deletions
+2 -1
View File
@@ -3,6 +3,7 @@ import { createFileRoute } from "@tanstack/react-router"
import { useEffect } from "react"
import { PanelsService } from "@/client"
import { appPath } from "@/lib/portal"
/**
* Adopting a screen that has no keyboard.
@@ -57,7 +58,7 @@ function PairPanel() {
// A full load rather than a route change: everything this page asked for
// was asked without a credential, and the socket has to dial again holding
// this one.
window.location.href = `/panel/${status.panel}`
window.location.href = appPath(`/panel/${status.panel}`)
}, [status])
return (