Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ main.js

# Local iteration tooling (live-agent probes, ad-hoc harness scripts)
.scratch/
.device-use/
7 changes: 0 additions & 7 deletions apps/web/src/features/simulator/api/simulator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ export const simulatorService = {
}
},

/** Check if a streaming session is alive for this workspace.
* Panel uses event-driven recovery now — this returns null.
* The sim:streamReady event provides the stream URL on startup. */
getStreamInfo: async (_workspaceId: string): Promise<StreamInfo | null> => {
return null;
},

/**
* Start streaming from a simulator. Fires q:command sim:start which
* returns immediately (ack). The actual stream URL arrives via
Expand Down
30 changes: 11 additions & 19 deletions apps/web/src/features/simulator/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ interface SimulatorStore {
phases: Record<string, SimPhaseLabel>;
}

// Drop both maps' entries for a workspace. Centralizes the "idle keys are
// absent" invariant — every transition to idle must go through this.
function withoutWorkspace(state: SimulatorStore, workspaceId: string): Partial<SimulatorStore> {
const { [workspaceId]: _s, ...sessions } = state.sessions;
const { [workspaceId]: _p, ...phases } = state.phases;
return { sessions, phases };
}

export const useSimulatorStatusStore = create<SimulatorStore>()((set, get) => ({
sessions: {},
phases: {},
Expand All @@ -96,13 +104,8 @@ export const useSimulatorStatusStore = create<SimulatorStore>()((set, get) => ({
return false;
}

// Idle entries are deleted rather than stored so the map stays small.
if (next.phase === "idle") {
set((s) => {
const { [workspaceId]: _s, ...restSessions } = s.sessions;
const { [workspaceId]: _p, ...restPhases } = s.phases;
return { sessions: restSessions, phases: restPhases };
});
set((s) => withoutWorkspace(s, workspaceId));
} else {
set((s) => ({
sessions: { ...s.sessions, [workspaceId]: next },
Expand All @@ -113,14 +116,8 @@ export const useSimulatorStatusStore = create<SimulatorStore>()((set, get) => ({
},

setSession: (workspaceId, phase) => {
// Respect the "idle keys are absent" invariant: when phase is idle,
// delete the key from both maps instead of writing it.
if (phase.phase === "idle") {
set((s) => {
const { [workspaceId]: _s, ...restSessions } = s.sessions;
const { [workspaceId]: _p, ...restPhases } = s.phases;
return { sessions: restSessions, phases: restPhases };
});
set((s) => withoutWorkspace(s, workspaceId));
} else {
set((s) => ({
sessions: { ...s.sessions, [workspaceId]: phase },
Expand All @@ -129,12 +126,7 @@ export const useSimulatorStatusStore = create<SimulatorStore>()((set, get) => ({
}
},

clearWorkspaceSession: (workspaceId) =>
set((s) => {
const { [workspaceId]: _s, ...restSessions } = s.sessions;
const { [workspaceId]: _p, ...restPhases } = s.phases;
return { sessions: restSessions, phases: restPhases };
}),
clearWorkspaceSession: (workspaceId) => set((s) => withoutWorkspace(s, workspaceId)),
}));

// ---------------------------------------------------------------------------
Expand Down
30 changes: 4 additions & 26 deletions apps/web/src/features/simulator/ui/DeviceFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ interface GenericShellSpec {
right: string;
bottom: string;
};
showDynamicIsland: boolean;
showCameraDot: boolean;
}

function getGenericShellSpec(deviceType: string | null | undefined): GenericShellSpec {
Expand All @@ -40,29 +38,15 @@ function getGenericShellSpec(deviceType: string | null | undefined): GenericShel
aspectRatio: "834 / 1194",
shellRadius: "2.75rem",
screenRadius: "2.1rem",
screenInsets: {
top: "2.1%",
left: "2.2%",
right: "2.2%",
bottom: "2.1%",
},
showDynamicIsland: false,
showCameraDot: true,
screenInsets: { top: "2.1%", left: "2.2%", right: "2.2%", bottom: "2.1%" },
};
}

return {
aspectRatio: "430 / 932",
shellRadius: "3.25rem",
screenRadius: "2.6rem",
screenInsets: {
top: "1.7%",
left: "2.5%",
right: "2.5%",
bottom: "1.9%",
},
showDynamicIsland: true,
showCameraDot: false,
screenInsets: { top: "1.7%", left: "2.5%", right: "2.5%", bottom: "1.9%" },
};
}

Expand Down Expand Up @@ -93,6 +77,7 @@ function FrameContainer({

function GenericDeviceFrame({ deviceType, children }: DeviceFrameProps) {
const shell = useMemo(() => getGenericShellSpec(deviceType), [deviceType]);
const isTablet = deviceType?.includes("iPad") ?? false;

return (
<FrameContainer aspectRatio={shell.aspectRatio}>
Expand All @@ -105,14 +90,7 @@ function GenericDeviceFrame({ deviceType, children }: DeviceFrameProps) {
aria-hidden="true"
/>

{shell.showDynamicIsland && (
<div
className="bg-bg-base pointer-events-none absolute top-[2.35%] left-1/2 z-20 h-[4.1%] w-[38%] -translate-x-1/2 rounded-full"
aria-hidden="true"
/>
)}

{shell.showCameraDot && (
{isTablet && (
<div
className="bg-bg-base ring-border/40 pointer-events-none absolute top-[1.45%] left-1/2 z-20 h-2.5 w-2.5 -translate-x-1/2 rounded-full ring-2"
aria-hidden="true"
Expand Down
Loading
Loading