Riftix Console Bridge

Host Chrome

Rules for the console's own first-party surfaces, so the shell and the apps it hosts read as one product.

The console is not only a frame — it has its own pages: organization settings, members, the app launcher, the admin area, the auth screens. Those pages sit directly beside embedded apps in the same navbar, so they are held to the same standard.

Everything in Foundations, Layout and Components applies to host pages unchanged. This page covers only what is specific to being the host.

What the host owns

These belong to the shell. An app must never draw them, and the host must always draw them.

SurfaceNotes
Top navbarFixed 40px, bg-navbar, three-column grid, not selectable
App launcherLeft column — the only cross-app navigation in the product
Breadcrumb trailCenter column, replaced wholesale when an app pushes its own
Organization switcherRight column
Theme toggleRight column — the single control for the whole console
Toast viewportOne stack for the console and every embedded app
Confirm modalCentered on the viewport, not on a panel

The corresponding rules for the app side are in Embedded apps.

<div class="bg-navbar grid h-10 select-none grid-cols-3 grid-rows-1 border-b">
  <div class="flex items-center justify-start pl-1.5">…launcher…</div>
  <div class="flex items-center justify-center">…breadcrumbs…</div>
  <div class="flex items-center justify-end">…org · theme…</div>
</div>
  • h-10 is a contract, not a style choice. Embedded apps size their own top bars to match it so the two rows read as one band of chrome.
  • --navbar is its own token. It equals --background in light and is the brand color in dark. Do not substitute bg-background.
  • select-none on chrome. Dragging a selection across a navbar is never intentional.
  • Client-pushed breadcrumbs replace the host-derived trail rather than appending to it. The app knows its own hierarchy; the host only knows the URL.

Host page shell

Host pages have no sidebar, so the shell is simpler than an app's — but the scroll contract is identical. The route root owns the scroll:

<div class="h-full overflow-auto p-8">
  <div class="mx-auto flex max-w-4xl flex-col gap-6">
    <header class="flex flex-col gap-1">
      <h1 class="font-heading text-xl font-semibold tracking-tight">Settings</h1>
      <p class="text-muted-foreground text-xs/relaxed">…</p>
    </header>
    …
  </div>
</div>

The console's own routes do not currently match that snippet — they use space-y-6 for rhythm, a text-2xl title and a text-sm description, all of which predate these guidelines and conflict with Spacing rhythm and The scale. The snippet above is the target; the existing pages are the backlog.

Two differences from the app-side Page primitive are real and unresolved: host pages use p-8 / max-w-4xl, apps use p-6 / max-w-5xl. These should converge on the app-side values, since there are far more app pages than host pages. Until that lands, do not mix the two within one surface.

Theme broadcast

The host owns the theme and must push every change to every connected app.

const next = theme === "light" ? "dark" : "light";
setTheme(next);
document.documentElement.classList.toggle("dark", next === "dark");
host.broadcastContextPatch({ theme: next });

Three rules:

  • Patch, never rebuild. Each channel merges the patch into its own context snapshot, which carries per-channel fields such as the mount path. Pushing a whole reconstructed snapshot from a toggle handler drops them.
  • Persist only explicit choices. A user who has never touched the toggle keeps following their OS preference; writing a value on first load silently opts them out of it forever.
  • Cross-origin iframes do not join a view transition. If the host crossfades its own chrome between palettes, embedded apps still swap on their own bridge schedule. Keep the crossfade short so the two do not visibly disagree, and skip it entirely under prefers-reduced-motion.

Framing an embedded app

The host is responsible for the app's first impression.

  • Cover the iframe until the handshake resolves, then fade out. Use a delayed fade-in on the spinner so a fast handshake never flashes one.
  • Use --muted for the overlay, so the transition is into the app rather than out of a white flash.
  • Keep the overlay non-interactive while fading, and unmount it afterwards.
  • The iframe itself is size-full border-0 with a --muted background.

Where the host may be more expressive

Host pages carry a little more personality than app pages, and there is a line.

The app launcher uses a short staggered entrance on its tiles. The reasoning in the source is the rule:

An occasional page, so a little delight is allowed.

Rule. Entrance animation belongs to landing and navigational surfaces that a user sees briefly and infrequently. It does not belong on data-dense pages, tables, or anything a user returns to many times an hour — there, the animation becomes a delay between the user and their work.

Guard any such entrance with a reduced-motion variant, and keep it under 300ms.

Token ownership

apps/console/src/styles.css is the source of truth for every console app.

Rule. A change to that file is a change to every app's vendored copy. Changes must be additive where possible, and announced where they are not. Removing or repurposing a token breaks apps you cannot see from this repository.

The long-term fix is to publish the theme as a package so apps consume it rather than copy it. Until then, the sync obligation is real and manual — see Source of truth.

On this page