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
+20
View File
@@ -42,3 +42,23 @@ export function isPortal(): boolean {
export function apiToken(): string {
return portalConfig()?.token ?? localStorage.getItem("access_token") ?? ""
}
/**
* A path in this app, spelled the way the browser has to spell it.
*
* The router is given the portal's basepath, so every `Link` and `navigate`
* carries it already. Anything that steps outside the router — a plain
* anchor, a `location.href` — has to add it back, or it lands on the portal
* itself, which serves nothing at that address.
*/
export function appPath(path: string): string {
return `${portalConfig()?.basePath ?? ""}${path}`
}
/** This page's route inside the app, with any portal prefix taken off again. */
export function appRoute(): string {
const base = portalConfig()?.basePath ?? ""
const path = window.location.pathname
if (!base || !path.startsWith(base)) return path
return path.slice(base.length) || "/"
}