eleven / messenger Public

web: signed-out root visits redirect to the home sign-in page (#732)

* web: signed-out root visits redirect to the home sign-in page

A bare browser visit with no session showed a near-empty landing card (on a
sealed instance, deliberately nameless). Members' spaces live in their home
vault, so send them there: showNoInvite now location.replace()s to
__HOME_ORIGIN__. The paste-to-join card survives where it's load-bearing —
the installed Home-Screen app (its only way to consume an invite link) — and
via ?stay=1 anywhere; native webviews were never routed here. Dev pages
without the substitution keep the card (origin sanity check).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* web: pre-paint the signed-out root bounce (no boot flash before the hop)

The redirect stays client-side by necessity — the session lives in
localStorage the server deliberately can't see — but firing it from
showNoInvite meant a full shell boot rendered before the hop. An inline
nonced head script now runs the same gate before first paint, so a signed-out
visit leaves without rendering anything; showNoInvite keeps the check as the
backstop for storage-denied/corrupt-session edges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell b04215adb8227ee52a53af5dad284446253ba914 parent 8f793b8
3 files changed, +41 −0
  • changelog/2026-07-11-root-redirect.md +4 −0
  • web/pages/index.html +22 −0
  • web/static/shell.js +15 −0
diff --git a/changelog/2026-07-11-root-redirect.md b/changelog/2026-07-11-root-redirect.md
new file mode 100644
index 0000000..152a311
--- /dev/null
+++ b/changelog/2026-07-11-root-redirect.md
@@ -0,0 +1,4 @@
+🤖 Opening a space's web address when you're not signed in now takes you
+straight to the Eleven sign-in page, where your saved spaces live — instead of
+a bare card. The installed Home-Screen app keeps its paste-your-invite-link
+screen, and invite links themselves work exactly as before.
diff --git a/web/pages/index.html b/web/pages/index.html
index bac8d77..80057c3 100644
--- a/web/pages/index.html
+++ b/web/pages/index.html
@@ -23,6 +23,28 @@
<script nonce="__CSP_NONCE__">
window.__homeOrigin = "__HOME_ORIGIN__";
</script>
+<!-- Pre-paint bounce: a signed-out browser visit belongs on home's sign-in
+ page. This must be client-side — the session lives in localStorage, which
+ the server (deliberately) cannot see, so an HTTP redirect would bounce
+ signed-in members too. Running it here, before first paint, makes it feel
+ like one: nothing renders before the hop. shell.js's showNoInvite keeps
+ the same check as a backstop (corrupt session, storage denied). Stays put:
+ native webviews (__lchatEmbed, injected at documentStart), the installed
+ Home-Screen app (paste-to-join is its only way to consume an invite
+ link), ?stay=1, restore returns from home, and unsubstituted dev pages. -->
+<script nonce="__CSP_NONCE__">
+ (function () {
+ try {
+ if (window.__lchatEmbed) return;
+ if (matchMedia("(display-mode: standalone)").matches || navigator.standalone === true) return;
+ if (localStorage.getItem("lchat_session")) return;
+ if (location.hash.indexOf("lchat_restore=") !== -1) return;
+ if (new URLSearchParams(location.search).has("stay")) return;
+ var home = window.__homeOrigin || "";
+ if (home.indexOf("http") === 0) location.replace(home);
+ } catch (e) {} /* storage denied etc.: route() decides after boot */
+ })();
+</script>
<link rel="stylesheet" href="/static/style.css" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/static/icon-192.png" />
diff --git a/web/static/shell.js b/web/static/shell.js
index eff0d5c..5a4acb0 100644
--- a/web/static/shell.js
+++ b/web/static/shell.js
@@ -10287,6 +10287,21 @@ function parseLink(text) {
}
function showNoInvite() {
+ // A bare browser visit with no session belongs on the home server's sign-in
+ // page — members recover their spaces there, new people learn what Eleven
+ // is, and the instance's landing reveals nothing (which sealed branding
+ // makes literal). The card below survives for the one surface that NEEDS
+ // it: the installed Home-Screen app consumes invite links by paste — it has
+ // no other way in — and ?stay=1 keeps it reachable everywhere (debugging,
+ // and a deliberate "no, this instance's page" bookmark). Native webviews
+ // never get here (route() hands embed to bootEmbed first). The origin check
+ // also skips a dev page served without __HOME_ORIGIN__ substitution.
+ const standalone =
+ matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
+ const home = window.__homeOrigin || "";
+ if (!standalone && home.startsWith("http") && !new URLSearchParams(location.search).has("stay")) {
+ return location.replace(home);
+ }
hideLoading();
app.appendChild(tpl("noinvite-tpl"));
// Public branding: reveal the server photo once /api/group confirms one is