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
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