Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions apps/web/src/lazurio/LazurioEnvironmentBrowser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
type ThreadRightPanelState,
useRightPanelStore,
} from "../rightPanelStore";
import { LazurioRightPanelSurfaces, useLazurioThreadTabs } from "./LazurioEnvironmentBrowser";
import {
ENVIRONMENT_ELEMENT,
LazurioRightPanelSurfaces,
useLazurioThreadTabs,
} from "./LazurioEnvironmentBrowser";
import {
resetThreadTabs,
syncOf,
Expand Down Expand Up @@ -294,22 +298,21 @@ describe("the panel's Environment browser frames", () => {
});

describe("another Environment's browser in the panel", () => {
const signInNotice = "Sign in to the Environment browser of Acme · vm-02";
const signInNotice = "Sign in to this Environment's browser";

it("frames it marked as that Environment's, telling it only this page's origin", async () => {
it("frames it marked as that Environment's, named by the shell, telling it only this page's origin", async () => {
withSnapshot(snapshot([{ tabId: id(1) }]));
await showPanel(threadA, [tab(1), foreignTab], foreignTab.id);
expect(frameOf(foreignView).getAttribute("referrerpolicy")).toBe("origin");
expect(frameOf(foreignView).title).toBe("Acme · vm-02");
expect(container.textContent).toContain("Acme · vm-02");
const picture = container.querySelector<HTMLImageElement>(
'img[src="https://github.com/acme.png?size=32"]',
);
expect(picture?.getAttribute("referrerpolicy")).toBe("no-referrer");
act(() => {
picture?.dispatchEvent(new Event("error"));
});
expect(container.textContent).toContain("AAcme · vm-02");
expect(frameOf(foreignView).title).toBe("Another Environment's browser");
// The shell's element names it as that Environment's switcher does; until the shell knows it
// (or without the element), it says only that the browser is another Environment's.
const name = container.querySelector(ENVIRONMENT_ELEMENT);
expect(name?.getAttribute("environment")).toBe("vm-02.acme");
expect(name?.getAttribute("lang")).toBe("en");
expect(name?.textContent).toBe("Another Environment's browser");
expect(container.textContent).not.toContain("Acme · vm-02");
expect(container.querySelector("img")).toBeNull();
expect(frameOf(viewOf(1)).title).toBe("Environment browser");
});

Expand Down
71 changes: 35 additions & 36 deletions apps/web/src/lazurio/LazurioEnvironmentBrowser.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { scopedThreadKey } from "@t3tools/client-runtime/environment";
import type { ScopedThreadRef } from "@t3tools/contracts";
import { ExternalLink, Globe2, LogIn, Plus, Users } from "lucide-react";
import { type ReactNode, useEffect, useRef, useState } from "react";
import { createElement, type ReactNode, useEffect, useRef, useState } from "react";

import { Button } from "~/components/ui/button";
import {
Expand All @@ -21,10 +21,9 @@ import { usePrimaryEnvironmentId } from "../state/environments";
import type { LazurioBrowserView } from "./browserView";
import {
foreignEnvironmentBrowserTab,
foreignEnvironmentIcon,
foreignEnvironmentLabel,
isPlainPrimaryClick,
readEnvironmentBrowserMessage,
viewEnvironmentId,
} from "./environmentBrowser";
import { NewTabSurface, type SurfaceAction, useNewTab, useNewTabDefined } from "./LazurioNewTab";
import {
Expand Down Expand Up @@ -219,21 +218,29 @@ export function useLazurioThreadTabs(
const FOREIGN_VIEW_AFTER_LOAD_MS = 4_000;
const FOREIGN_VIEW_MS = 15_000;

/** The GitHub picture of the Organization or person whose Environment it is, else its initial. */
function EnvironmentOwnerPicture(props: { readonly environment: LazurioBrowserView }) {
const [failed, setFailed] = useState(false);
return failed ? (
<span className="flex size-4 shrink-0 items-center justify-center rounded-sm bg-warning font-semibold text-3xs text-white">
{props.environment.owner.charAt(0).toUpperCase()}
</span>
) : (
<img
src={foreignEnvironmentIcon(props.environment)}
alt=""
referrerPolicy="no-referrer"
className="size-4 shrink-0 rounded-sm"
onError={() => setFailed(true)}
/>
/** The shell's element that names an Environment as its switcher does (LazurioPlatform F39,
* addendum of 2026-10-12). */
export const ENVIRONMENT_ELEMENT = "lazurio-environment";

/** Another Environment's view, said without naming it: what the head shows until the shell knows
* that Environment, and where the Environment's Platform predates the element. */
const anotherEnvironment = (czech: boolean) =>
czech ? "Prohlížeč jiného Environmentu" : "Another Environment's browser";

/** Another Environment's name in the panel's head: the shell's `<lazurio-environment>`, which
* shows its children until the page's shell knows that Environment. */
function ForeignEnvironmentName(props: {
readonly environment: LazurioBrowserView;
readonly czech: boolean;
}) {
return createElement(
ENVIRONMENT_ELEMENT,
{
environment: viewEnvironmentId(props.environment),
lang: props.czech ? "cs" : "en",
className: "min-w-0 flex-1 truncate font-medium text-warning-foreground text-xs",
},
anotherEnvironment(props.czech),
);
}

Expand Down Expand Up @@ -466,7 +473,7 @@ function LazurioEnvironmentBrowser(props: {
}, [isForeign, view, request, loaded, cameUp]);
const signIn = isForeign && Boolean(view) && late === request && cameUp !== request;
const czech = prefersCzech();
const label = foreign === null ? null : foreignEnvironmentLabel(foreign.environment, czech);
const title = foreign === null ? "Environment browser" : anotherEnvironment(czech);

return (
<div className={cn("flex min-h-0 flex-1 flex-col", isForeign && "border-2 border-warning")}>
Expand All @@ -477,21 +484,13 @@ function LazurioEnvironmentBrowser(props: {
)}
>
{foreign === null ? (
<Globe2 aria-hidden className="size-3.5 shrink-0 text-muted-foreground" />
<>
<Globe2 aria-hidden className="size-3.5 shrink-0 text-muted-foreground" />
<span className="min-w-0 flex-1 truncate text-muted-foreground text-xs">{title}</span>
</>
) : (
<EnvironmentOwnerPicture
key={foreign.environment.owner}
environment={foreign.environment}
/>
<ForeignEnvironmentName environment={foreign.environment} czech={czech} />
)}
<span
className={cn(
"min-w-0 flex-1 truncate text-xs",
isForeign ? "font-medium text-warning-foreground" : "text-muted-foreground",
)}
>
{label ?? "Environment browser"}
</span>
<Tooltip>
<TooltipTrigger
render={
Expand Down Expand Up @@ -526,7 +525,7 @@ function LazurioEnvironmentBrowser(props: {
key={request}
ref={frame}
src={view}
title={label ?? "Environment browser"}
title={title}
className="min-h-0 w-full flex-1 border-0"
allow="clipboard-read; clipboard-write; fullscreen"
// oxlint-disable-next-line react/iframe-missing-sandbox -- the view is never on this page's origin (environmentBrowser.ts), so allow-same-origin keeps it to its own.
Expand All @@ -535,7 +534,7 @@ function LazurioEnvironmentBrowser(props: {
referrerPolicy="origin"
onLoad={() => setLoaded(request)}
/>
{signIn && label !== null ? (
{signIn && foreign !== null ? (
<div className="absolute inset-0 flex bg-background">
<Empty size="compact">
<EmptyMedia variant="icon">
Expand All @@ -544,8 +543,8 @@ function LazurioEnvironmentBrowser(props: {
<EmptyHeader>
<EmptyTitle>
{czech
? `Přihlas se do prohlížeče Environmentu ${label}`
: `Sign in to the Environment browser of ${label}`}
? "Přihlas se do prohlížeče tohoto Environmentu"
: "Sign in to this Environment's browser"}
</EmptyTitle>
<EmptyDescription>
{czech
Expand Down
25 changes: 8 additions & 17 deletions apps/web/src/lazurio/environmentBrowser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {
environmentBrowserLink,
environmentBrowserTab,
foreignEnvironmentBrowserTab,
foreignEnvironmentIcon,
foreignEnvironmentLabel,
isPlainPrimaryClick,
readEnvironmentBrowserMessage,
viewEnvironmentId,
} from "./environmentBrowser";

const origin = "https://t3code.vm-01.example.lazurio.io";
Expand Down Expand Up @@ -177,24 +176,16 @@ describe("another Lazurio Environment's tab", () => {
}
});

it("is named by its Organization and Environment, or as the person's own", () => {
const named = (address: string, czech: boolean) => {
it("hands the shell the id of its Environment, the view's host between browser. and lazurio.io", () => {
const idOf = (address: string) => {
const tab = tabOf(address);
if (tab === null) throw new Error(`${address} is no other Environment's tab`);
return foreignEnvironmentLabel(tab.environment, czech);
return viewEnvironmentId(tab.environment);
};
expect(named(organization, true)).toBe("Acme · vm-02");
expect(named(organization, false)).toBe("Acme · vm-02");
expect(named(personal, true)).toBe("Osobní · jana");
expect(named(personal, false)).toBe("Personal · jana");
// More labels: the Organization is the one before lazurio.io, the Environment after browser.
expect(named(`https://browser.a.b.c.lazurio.io/t/${foreignId}`, false)).toBe("C · a");
expect(foreignEnvironmentIcon(tabOf(organization)!.environment)).toBe(
"https://github.com/acme.png?size=32",
);
expect(foreignEnvironmentIcon(tabOf(personal)!.environment)).toBe(
"https://github.com/jana.png?size=32",
);
expect(idOf(organization)).toBe("vm-02.acme");
expect(idOf(personal)).toBe("jana");
// The shell decides what an id names; the fork reads no Organization or Machine out of it.
expect(idOf(`https://browser.a.b.c.lazurio.io/t/${foreignId}`)).toBe("a.b.c");
});
});

Expand Down
20 changes: 6 additions & 14 deletions apps/web/src/lazurio/environmentBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,13 @@ function parentDomainOf(origin: string): string | null {
}

/**
* How the panel names another Environment: `<Org> · <environment>` for an Organization's,
* `Osobní · <login>` or `Personal · <login>` for a person's own Environment.
* The id by which the Environment's shell names the Environment of a view: its view's host
* between `browser.` and `.lazurio.io` (`vm-02.acme`, or `jana` for a person's own). The panel
* hands it to the shell's `<lazurio-environment>`, which names that Environment as its switcher
* does (LazurioPlatform F39, addendum of 2026-10-12); the fork has no naming rule of its own.
*/
export function foreignEnvironmentLabel(environment: LazurioBrowserView, czech: boolean): string {
const owner = environment.personal
? czech
? "Osobní"
: "Personal"
: `${environment.owner.charAt(0).toUpperCase()}${environment.owner.slice(1)}`;
return `${owner} · ${environment.environment}`;
}

/** The GitHub picture of the Organization, or of the person, whose Environment it is. */
export const foreignEnvironmentIcon = (environment: LazurioBrowserView) =>
`https://github.com/${environment.owner}.png?size=32`;
export const viewEnvironmentId = (environment: LazurioBrowserView): string =>
new URL(environment.origin).hostname.slice("browser.".length, -".lazurio.io".length);

/** A plain primary click follows a link in place; modifiers or other buttons open it elsewhere. */
export function isPlainPrimaryClick(
Expand Down
19 changes: 11 additions & 8 deletions docs/operations/lazurio-fork-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,18 +433,21 @@ T3 žádný prohlížeč nemá, a tak overlay dělá tohle:
starší snímek ji nevrátí (bez Vrátit, záložka už není). Záložka panelu ukazuje
místo „Browser“ titulek, který pohled hlásí (jinak host adresy stránky);
titulky žijí jen v paměti stránky.
- **Záložka jiného Environmentu.** Je nezaměnitelně označená: barevný okraj a
hlavička s obrázkem Organizace z GitHubu
(`https://github.com/<org-or-login>.png?size=32`, jinak její iniciála) a
jménem `<Org> · <environment>`, tedy label před `lazurio.io` s velkým prvním
písmenem a label za `browser.` (například `Acme · vm-01`). Osobní Environment
je `Osobní · <login>`, anglicky `Personal · <login>`; jazyk volí overlay jako
- **Záložka jiného Environmentu.** Je nezaměnitelně označená barevným okrajem
a hlavičkou, která ten Environment jmenuje prvkem shellu
`<lazurio-environment environment="<id>" lang>` (LazurioPlatform F39, dodatek
z 2026-10-12): stejným jménem, znakem a řádkem „pro koho“ jako přepínač jeho
aplikací, ze stavu shellu této stránky. Id je host pohledu mezi `browser.` a
`.lazurio.io` (`viewEnvironmentId`, například `vm-01.acme` nebo `jana`).
Vlastní pravidlo pojmenování fork nemá. Dokud shell stránky ten Environment
nezná, a na Platformě bez prvku, ukáže hlavička jen „Prohlížeč jiného
Environmentu“; přesné jméno má pod ní lišta pohledu. Jazyk volí overlay jako
pohled sám, podle jazyka prohlížeče (`cs`). Rám posílá
`referrerPolicy="origin"`, podle kterého pohled jiného Environmentu dovolí
zarámování T3 nebo Launchpadu (změna LazurioPlatform). Když pohled do 4 s od
načtení rámu (15 s, když se nenačte) neohlásí svou záložku, typicky bez
sezení v bráně toho Environmentu, ukáže panel „Přihlas se do prohlížeče
Environmentu <label>“ s otevřením pohledu v nové záložce a Reload: přihlašovací
sezení v bráně toho Environmentu, ukáže panel „Přihlas se do prohlížeče tohoto
Environmentu“ s otevřením pohledu v nové záložce a Reload: přihlašovací
stránka Lazuria se v rámu nevykreslí. „Open in new tab“ v hlavičce zůstává.
- **Rámy zůstávají připojené.** Dokud pravý panel ukazuje vlákno, má každá jeho
záložka prohlížeče Environmentu svůj rám připojený, i když je vpředu jiná
Expand Down
Loading