mirror of
https://github.com/iamlukethedev/Claw3D.git
synced 2026-07-31 03:52:15 +00:00
fix(floor-nav): show only available floors per active adapter; block hang on unconfigured runtime
- floors.ts: add paperclip to FloorProvider; add listAvailableFloorsForAdapter() — lobby always visible, runtime floors only shown when their provider matches the active adapter, so demo-only users only see Lobby - OfficeFloorNav: accept activeAdapterType prop, filter floor list via listAvailableFloorsForAdapter(); fall back displayActiveFloorId to lobby if current floor is no longer in the available set - OfficeScreen: pass selectedAdapterType to OfficeFloorNav; add guard in handleSelectFloor — bail back to lobby immediately when a runtime floor has no gateway URL configured, preventing the connect-hang limbo state Authored-By: GSKNNFT
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
OFFICE_FLOORS,
|
||||
getAdjacentEnabledOfficeFloorId,
|
||||
listOfficeFloorsForZone,
|
||||
listAvailableFloorsForAdapter,
|
||||
type FloorDefinition,
|
||||
type FloorId,
|
||||
type FloorProvider,
|
||||
} from "@/lib/office/floors";
|
||||
import type { FloorRosterState } from "@/lib/office/floorRoster";
|
||||
|
||||
@@ -11,12 +12,15 @@ type OfficeFloorNavProps = {
|
||||
activeFloorId: FloorId;
|
||||
floorRosterCache: Record<FloorId, FloorRosterState>;
|
||||
onSelectFloor: (floorId: FloorId) => void;
|
||||
/** The currently selected adapter — controls which runtime floors are shown */
|
||||
activeAdapterType?: FloorProvider | null;
|
||||
};
|
||||
|
||||
const PROVIDER_LABEL: Record<FloorDefinition["provider"], string> = {
|
||||
const PROVIDER_LABEL: Record<FloorProvider, string> = {
|
||||
demo: "Demo",
|
||||
openclaw: "OpenClaw",
|
||||
hermes: "Hermes",
|
||||
paperclip: "Paperclip",
|
||||
custom: "Custom",
|
||||
local: "Local",
|
||||
claw3d: "Claw3D",
|
||||
@@ -86,10 +90,18 @@ export function OfficeFloorNav({
|
||||
activeFloorId,
|
||||
floorRosterCache,
|
||||
onSelectFloor,
|
||||
activeAdapterType,
|
||||
}: OfficeFloorNavProps) {
|
||||
const buildingFloors = listOfficeFloorsForZone("building");
|
||||
const outsideFloors = listOfficeFloorsForZone("outside");
|
||||
const activeFloor = OFFICE_FLOORS.find((floor) => floor.id === activeFloorId) ?? OFFICE_FLOORS[0];
|
||||
const availableFloors = listAvailableFloorsForAdapter(activeAdapterType ?? null);
|
||||
const buildingFloors = availableFloors.filter((f) => f.zone === "building");
|
||||
const outsideFloors = availableFloors.filter((f) => f.zone === "outside");
|
||||
|
||||
// Active floor — fall back to lobby if current floor is no longer available
|
||||
const activeIsAvailable = availableFloors.some((f) => f.id === activeFloorId);
|
||||
const displayActiveFloorId = activeIsAvailable ? activeFloorId : "lobby";
|
||||
|
||||
const activeFloor =
|
||||
OFFICE_FLOORS.find((floor) => floor.id === displayActiveFloorId) ?? OFFICE_FLOORS[0];
|
||||
const activeRoster = floorRosterCache[activeFloor.id];
|
||||
|
||||
return (
|
||||
@@ -121,7 +133,7 @@ export function OfficeFloorNav({
|
||||
Building
|
||||
</div>
|
||||
{buildingFloors.map((floor) =>
|
||||
renderFloorButton({ floor, activeFloorId, floorRosterCache, onSelectFloor }),
|
||||
renderFloorButton({ floor, activeFloorId: displayActiveFloorId, floorRosterCache, onSelectFloor }),
|
||||
)}
|
||||
</div>
|
||||
{outsideFloors.length > 0 ? (
|
||||
@@ -130,7 +142,7 @@ export function OfficeFloorNav({
|
||||
Outside
|
||||
</div>
|
||||
{outsideFloors.map((floor) =>
|
||||
renderFloorButton({ floor, activeFloorId, floorRosterCache, onSelectFloor }),
|
||||
renderFloorButton({ floor, activeFloorId: displayActiveFloorId, floorRosterCache, onSelectFloor }),
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -80,6 +80,7 @@ import {
|
||||
listOfficeFloorsForProvider,
|
||||
resolveActiveOfficeFloorId,
|
||||
type FloorId,
|
||||
type FloorProvider,
|
||||
} from "@/lib/office/floors";
|
||||
import {
|
||||
AgentEditorModal,
|
||||
@@ -1481,6 +1482,15 @@ export function OfficeScreen({
|
||||
console.error("Failed to resolve floor runtime profile.", error);
|
||||
}
|
||||
|
||||
// Guard: if this is a runtime floor and there's no gateway URL to connect to,
|
||||
// bail back to lobby rather than entering a connect-hang limbo state.
|
||||
if (floor.kind === "runtime" && !nextGatewayUrl.trim()) {
|
||||
setActiveFloorId("lobby");
|
||||
settingsCoordinator.schedulePatch({ activeFloorId: "lobby" }, 0);
|
||||
setAgentsLoaded(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedAdapterType(adapterType);
|
||||
setGatewayUrl(nextGatewayUrl);
|
||||
setToken(nextToken);
|
||||
@@ -4741,6 +4751,7 @@ export function OfficeScreen({
|
||||
onSelectFloor={(floorId) => {
|
||||
void handleSelectFloor(floorId);
|
||||
}}
|
||||
activeAdapterType={(selectedAdapterType as FloorProvider) ?? null}
|
||||
/>
|
||||
<section className="relative h-full min-h-0 min-w-0 overflow-hidden">
|
||||
<RetroOffice3D
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type FloorProvider =
|
||||
| "openclaw"
|
||||
| "hermes"
|
||||
| "paperclip"
|
||||
| "custom"
|
||||
| "demo"
|
||||
| "local"
|
||||
@@ -162,6 +163,32 @@ export const resolveActiveOfficeFloorId = (floorId: FloorId | null | undefined):
|
||||
return listEnabledOfficeFloors()[0]?.id ?? DEFAULT_ACTIVE_FLOOR_ID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Floors visible in the nav for a given active adapter.
|
||||
* - Lobby (kind="lobby") always shown.
|
||||
* - Runtime floors shown only when their provider matches the active adapter.
|
||||
* - Non-runtime enabled floors (training, market, campus) always shown.
|
||||
*
|
||||
* When activeAdapterType is null/undefined/"demo", only lobby + non-runtime floors appear.
|
||||
*/
|
||||
export const listAvailableFloorsForAdapter = (
|
||||
activeAdapterType: FloorProvider | "demo" | null | undefined,
|
||||
): FloorDefinition[] => {
|
||||
return OFFICE_FLOORS.filter((floor) => {
|
||||
if (!floor.enabled) return false;
|
||||
if (floor.kind === "lobby") return true;
|
||||
if (floor.kind === "runtime") {
|
||||
return (
|
||||
Boolean(activeAdapterType) &&
|
||||
activeAdapterType !== "demo" &&
|
||||
floor.provider === activeAdapterType
|
||||
);
|
||||
}
|
||||
// training / market / campus
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
export const getAdjacentEnabledOfficeFloorId = (
|
||||
floorId: FloorId,
|
||||
direction: 1 | -1,
|
||||
|
||||
Reference in New Issue
Block a user