Give / somewhere to go on every shape of deployment - #1
Merged
holzmanshmuel merged 1 commit intoAug 26, 2026
Conversation
`/` rendered one fixed placeholder whose copy assumed a single-business install
— "The booking page for this business isn't live yet" — which on a multi-tenant
instance is true of nothing, and it offered no link anywhere. That made it a
dead end, and a load-bearing one: not-found.tsx sends every visitor who follows
a stale link to `/`, so the escape hatch from a 404 was a wall.
There is no single true `/` here, because there is no single shape of
deployment. So ask, and answer accordingly:
empty — nothing created yet, so the visitor is the operator who just
deployed this. Send them to sign in and create the first business.
single — the common self-hosted case: one salon, one box. `/` *is* that
shop's booking page, so go there. No env var, no configuration —
one business is one business.
multi — say what Torim is, point owners at sign-in, and tell customers to
use the link their business gave them.
The multi case deliberately does not list the businesses, and says so on the
page: people who trim a URL are owed a way forward, not a directory of everyone
hosted alongside them. `describeInstance()` stops at `LIMIT 2` — two rows is all
it takes to tell none from one from many — so that list is never assembled and
therefore cannot leak, however large the instance grows.
The ticket assumed the single-business redirect was blocked by RLS. It is not:
`torim.businesses` is one of the three tables deliberately outside RLS (see the
header of scripts/sql/001_tenancy.sql) precisely because a public booking slug
has to resolve before any tenant context exists. Reading it through the existing
systemQuery path routes around no policy, so neither an env-configured landing
path nor a new SECURITY DEFINER function is needed.
`/` is also the one route that must never 500, so a database that does not
answer degrades to the shared landing rather than throwing. That page is true of
every instance — it names no business and promises nothing about this
deployment — so a single-business install loses one redirect while the database
is down, and nobody reads a false sentence.
Verified against a real Postgres in all four states: empty renders the first-run
page (200), one business 307s to `/b/<slug>`, two render the landing with no
tenant name in the HTML, and an unreachable database renders the landing rather
than a 500. Hebrew renders RTL throughout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZy5uLuHnuieYGrjRdwZCa
holzmanshmuel
deleted the
holzmanshmuel/holzman-135-torim-the-root-page-is-a-dead-end-on-a-multi-tenant
branch
August 26, 2026 14:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes HOLZMAN-135.
/rendered one fixed placeholder whose copy assumed a single-business install — "The booking page for this business isn't live yet" — which on a multi-tenant instance is true of nothing, and it offered no link anywhere. That made it a dead end, and a load-bearing one:not-found.tsxsends every visitor who follows a stale link to/, so the escape hatch from a 404 was a wall.The ticket's stated blocker doesn't hold
HOLZMAN-135 assumed the single-business redirect was blocked by RLS. It isn't —
torim.businessesis one of the three tables deliberately outside RLS (see the header ofscripts/sql/001_tenancy.sql), precisely because a public booking slug has to resolve before any tenant context exists.findBusinessBySlug()already reads it throughsystemQueryon every booking page load.So neither proposed workaround was needed: no env-configured landing path, no new
SECURITY DEFINERfunction.What this does
/login/b/<slug>. The common self-hosted case: one salon, one boxNew
describeInstance()usesSELECT slug … LIMIT 2. Two rows is all it takes to tell none from one from many, so a tenant list is never assembled and therefore cannot leak, however large the instance grows. The multi page states that as a property rather than silently omitting a directory./is also the one route that must never 500, so an unreachable database degrades to the shared landing — true of every instance, names no business — rather than throwing.The false string
home.descriptionis gone from both dictionaries.Verified
Against a real Postgres on the production build, all four states: 0 businesses → 200 first-run page; 1 → 307 to
/b/sole-salon, following through to a rendered booking page; 2 → 200 landing with 0 grep matches for either tenant's name or slug in the HTML; database unreachable → 200 landing, error logged, no 500. Hebrew renders RTL throughout.Suite 512 passed / 39 files (baseline 508/38 — the 4 new ones are
src/lib/instance-shape.test.ts).🤖 Generated with Claude Code
https://claude.ai/code/session_01YZy5uLuHnuieYGrjRdwZCa