+
+
+ {t('deck_view')}
+
+
+ {t('step', { number: selectedCommandIndex })}
+
+
+
+
+
+ {blockTargetTemp != null
+ ? t('temperature', { temp: blockTargetTemp })
+ : t('deactivated')}
+
+
+
+
+ {lidTargetTemp != null
+ ? t('temperature', { temp: lidTargetTemp })
+ : t('deactivated')}
+
+
+
+
+
+
+ )
+ break
+ }
+ case HEATERSHAKER_MODULE_TYPE: {
+ const { targetSpeed, targetTemp, latchOpen } = moduleState
+ moduleDetails = (
+
+
+
+ {targetTemp != null
+ ? t('temperature', { temp: targetTemp })
+ : t('deactivated')}
+
+
+
+
+ {targetSpeed != null
+ ? t('speed', { speed: targetSpeed })
+ : t('idle')}
+
+
+
+
+
+
+ )
+ break
+ }
+ case MAGNETIC_MODULE_TYPE: {
+ const { engaged } = moduleState
+ moduleDetails = (
+
+
+
+
+
+ )
+ break
+ }
+ case TEMPERATURE_MODULE_TYPE: {
+ const { status, targetTemperature } = moduleState
+ moduleDetails = (
+
+
+
+ {targetTemperature != null
+ ? t('temperature', { temp: targetTemperature })
+ : t('deactivated')}
+
+
+
+ )
+ break
+ }
+ case ABSORBANCE_READER_TYPE: {
+ const { lidOpen, initialization } = moduleState
+ moduleDetails = (
+
+
+
+
+ {initialization != null ? (
+ <>
+
+
+ {t('initialization')}
+
+
+ {initialization.mode}
+
+
+
+
+ {t('wavelengths')}
+
+
+ {initialization.wavelengths}
+
+
+ {initialization.referenceWavelength != null ? (
+
+
+ {t('reference_wavelength')}
+
+
+ {initialization.referenceWavelength}
+
+
+ ) : null}
+ >
+ ) : null}
+
+ )
+ break
+ }
+ case FLEX_STACKER_MODULE_TYPE: {
+ // TODO: add this in when the flex stacker module state is finalized for PD
+ // const {
+ // maxPoolCount,
+ // storedLabwareDetails,
+ // labwareInHopper,
+ // labwareOnShuttle,
+ // } = moduleState
+ console.error(
+ "TODO: update this when PD's flex stacker module state is finalized"
+ )
+ break
+ }
+ case MAGNETIC_BLOCK_TYPE: {
+ // no state to show
+ break
+ }
+ default:
+ console.error(
+ `ran into the default moduleContainer moduleState with module ${moduleDisplayName}`
+ )
+ }
+
+ return (
+
+
+
+ {moduleDisplayName}
+
+ {moduleDetails}
+
+
+ )
+}
diff --git a/app/src/organisms/Desktop/ProtocolVisualization/ModuleStatusContainer.tsx b/app/src/organisms/Desktop/ProtocolVisualization/ModuleStatusContainer.tsx
new file mode 100644
index 00000000000..6896a995759
--- /dev/null
+++ b/app/src/organisms/Desktop/ProtocolVisualization/ModuleStatusContainer.tsx
@@ -0,0 +1,22 @@
+import { useTranslation } from 'react-i18next'
+
+import { StyledText } from '@opentrons/components'
+
+import styles from './modulecontainer.module.css'
+
+interface ModuleStatusContainerProps {
+ title: string
+ children: React.ReactNode
+}
+export const ModuleStatusContainer = (
+ props: ModuleStatusContainerProps
+): JSX.Element => {
+ const { t } = useTranslation('protocol_visualization')
+ const { title, children } = props
+ return (
+
+ {t(title)}
+ {children}
+
+ )
+}
diff --git a/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/index.tsx b/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/index.tsx
new file mode 100644
index 00000000000..8fc017e929e
--- /dev/null
+++ b/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/index.tsx
@@ -0,0 +1,126 @@
+import { useTranslation } from 'react-i18next'
+
+import {
+ Divider,
+ MODULE_ICON_NAME_BY_TYPE,
+ RobotInfoLabel,
+} from '@opentrons/components'
+import { getIsTiprack } from '@opentrons/shared-data'
+import { getFullStackFromLabwares } from '@opentrons/step-generation'
+
+import { SlotDetailsEmptyState } from '/app/molecules/SlotDetailsEmptyState'
+
+import { LabwareSlotContainer } from '../LabwareSlotContainer'
+import { ModuleContainer } from '../ModuleContainer'
+import { TipDisposalContainer } from '../TipDisposalContainer'
+import { TipPickupContainer } from '../TipPickupContainer'
+import styles from './slotdetails.module.css'
+
+import type {
+ Liquid,
+ ProtocolAnalysisOutput,
+ RunTimeCommand,
+} from '@opentrons/shared-data'
+import type { InvariantContext, RobotState } from '@opentrons/step-generation'
+
+interface SlotDetailsProps {
+ slotId: string
+ command: RunTimeCommand
+ robotState: RobotState
+ invariantContext: InvariantContext
+ analysis: ProtocolAnalysisOutput
+ liquids: Liquid[]
+}
+export function SlotDetails(props: SlotDetailsProps): JSX.Element {
+ const { slotId, command, robotState, invariantContext, analysis, liquids } =
+ props
+ const { labware, modules } = robotState
+ const {
+ labwareEntities,
+ trashBinEntities,
+ wasteChuteEntities,
+ moduleEntities,
+ pipetteEntities,
+ } = invariantContext
+ const { commands } = analysis
+ const { t } = useTranslation('protocol_visualization')
+ const stackOfLabwareOnSlot = getFullStackFromLabwares(labware, slotId)
+ const moduleOnSlot = Object.entries(modules).find(
+ ([id, module]) => module.slot === slotId
+ )
+ const topMostLabwareOnSlot =
+ stackOfLabwareOnSlot?.length > 1 ? stackOfLabwareOnSlot[0] : null
+ const isTopmostLabwareATiprack =
+ topMostLabwareOnSlot != null &&
+ getIsTiprack(labwareEntities[topMostLabwareOnSlot].def)
+ const isTrashOnSlot =
+ Object.values(trashBinEntities).some(
+ trash => trash.location.split('cutout')[1] === slotId
+ ) ||
+ Object.values(wasteChuteEntities).some(
+ trash => trash.location.split('cutout')[1] === slotId
+ ) ||
+ slotId === 'fixedTrash'
+
+ const isSlotEmpty =
+ moduleOnSlot == null && topMostLabwareOnSlot == null && !isTrashOnSlot
+
+ return (
+ <>
+ {isSlotEmpty ? (
+
+
+
+ ) : null}
+
+
+
+
+
+ {moduleOnSlot != null ? (
+
+ ) : null}
+
+
+
+ {topMostLabwareOnSlot != null && isTopmostLabwareATiprack ? (
+
+ ) : null}
+ {topMostLabwareOnSlot != null && !isTopmostLabwareATiprack ? (
+
+ ) : null}
+ {isTrashOnSlot ? (
+
+ ) : null}
+ {moduleOnSlot != null ? (
+
+ ) : null}
+
+
+ >
+ )
+}
diff --git a/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/slotdetails.module.css b/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/slotdetails.module.css
new file mode 100644
index 00000000000..35588fab9b7
--- /dev/null
+++ b/app/src/organisms/Desktop/ProtocolVisualization/SlotDetails/slotdetails.module.css
@@ -0,0 +1,40 @@
+.slot_detail_container {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: var(--spacing-16) var(--spacing-20);
+ background-color: var(--white);
+}
+
+.slot_container {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ flex-direction: column;
+ gap: var(--spacing-8);
+}
+
+.slot_details {
+ height: 100%;
+ border-radius: var(--border-radius-8);
+ background-color: var(--white);
+ overflow-y: auto;
+}
+
+.slot_details::-webkit-scrollbar {
+ display: none;
+}
+
+.command_step_header {
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-16);
+}
+
+.slot_detail_header {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-4);
+}
diff --git a/app/src/organisms/Desktop/ProtocolVisualization/StepDetailContainer.tsx b/app/src/organisms/Desktop/ProtocolVisualization/StepDetailContainer.tsx
index 485ec81f843..32d3b2c0afb 100644
--- a/app/src/organisms/Desktop/ProtocolVisualization/StepDetailContainer.tsx
+++ b/app/src/organisms/Desktop/ProtocolVisualization/StepDetailContainer.tsx
@@ -6,11 +6,9 @@ import { PipetteContainer } from './PipetteContainer'
import styles from './stepdetailcontainer.module.css'
import { TipDisposalContainer } from './TipDisposalContainer'
import { TipPickupContainer } from './TipPickupContainer'
-import {
- getActiveSlotForLabwareDetails,
- getActiveSlotForTiprackDetails,
- getIsPipetteActive,
-} from './utils'
+import { getActiveSlotForLabwareDetails } from './utils/getActiveSlotForLabwareDetails'
+import { getActiveSlotForTiprackDetails } from './utils/getActiveSlotForTiprackDetails'
+import { getIsPipetteActive } from './utils/getIsPipetteActive'
import type { Liquid, RunTimeCommand } from '@opentrons/shared-data'
import type { InvariantContext, RobotState } from '@opentrons/step-generation'
diff --git a/app/src/organisms/Desktop/ProtocolVisualization/TipDisposalContainer.tsx b/app/src/organisms/Desktop/ProtocolVisualization/TipDisposalContainer.tsx
index 045bed58d43..620f55b5667 100644
--- a/app/src/organisms/Desktop/ProtocolVisualization/TipDisposalContainer.tsx
+++ b/app/src/organisms/Desktop/ProtocolVisualization/TipDisposalContainer.tsx
@@ -33,10 +33,10 @@ export function TipDisposalContainer({