diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json
index b466fd7f3442..822963459ce3 100644
--- a/app/src/assets/localization/en/labware_position_check.json
+++ b/app/src/assets/localization/en/labware_position_check.json
@@ -52,6 +52,7 @@
"ensure_nozzle_position_desktop": "Ensure that the {{tip_type}} is centered above and level with {{item_location}}. If it isn't, use the controls below or your keyboard to jog the pipette until it is properly aligned.",
"ensure_nozzle_position_odd": "Ensure that the {{tip_type}} is centered above and level with {{item_location}}. If it isn't, tap Move pipette and then jog the pipette until it is properly aligned.",
"ensure_probe_attached": "Ensure it is properly attached before proceeding.",
+ "ensure_tip_rack_accurately_placed": "Ensure the tip rack is accurately placed in the slot as outlined above to prevent damage to your labware.",
"exit": "Exit",
"exit_screen_confirm_exit": "Exit and discard all labware offsets",
"exit_screen_go_back": "Go back to labware position check",
diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/PlaceItemInstruction.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/PlaceItemInstruction.tsx
index 9033ca4bff1f..e1dd952299bf 100644
--- a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/PlaceItemInstruction.tsx
+++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/PlaceItemInstruction.tsx
@@ -1,7 +1,15 @@
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
+import { css } from 'styled-components'
-import { TYPOGRAPHY, LegacyStyledText } from '@opentrons/components'
+import {
+ TYPOGRAPHY,
+ LegacyStyledText,
+ Flex,
+ DIRECTION_COLUMN,
+ SPACING,
+ RESPONSIVENESS,
+} from '@opentrons/components'
import {
selectIsSelectedLwTipRack,
@@ -9,6 +17,7 @@ import {
OFFSET_KIND_DEFAULT,
selectLwDisplayName,
getFlexSlotNameOnly,
+ selectActivePipetteChannelCount,
} from '/app/redux/protocol-runs'
import { UnorderedList } from '/app/molecules/UnorderedList'
import { DescriptionContent } from '/app/molecules/InterventionModal'
@@ -23,6 +32,7 @@ import type {
import type { State } from '/app/redux/types'
import type { LPCWizardContentProps } from '/app/organisms/LabwarePositionCheck/types'
import type { EditOffsetContentProps } from '/app/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset'
+import { InlineNotification } from '/app/atoms/InlineNotification'
export function PlaceItemInstruction(
props: EditOffsetContentProps
@@ -33,11 +43,14 @@ export function PlaceItemInstruction(
const { protocolData } = useSelector(
(state: State) => state.protocolRuns[runId]?.lpc as LPCWizardState
)
+ const isActivePipette96ch =
+ useSelector(selectActivePipetteChannelCount(runId)) === 96
const isLwTiprack = useSelector(selectIsSelectedLwTipRack(runId))
const selectedLwInfo = useSelector(
selectSelectedLwOverview(runId)
) as SelectedLwOverview
const offsetLocationDetails = selectedLwInfo.offsetLocationDetails as OffsetLocationDetails
+ const isDefaultOffset = offsetLocationDetails.kind === OFFSET_KIND_DEFAULT
const buildHeader = (): string =>
t('prepare_item_in_location', {
@@ -57,35 +70,52 @@ export function PlaceItemInstruction(
) as LabwareStackupDetail[]
return (
- ,
- ...lwOnlyLocSeq.map((component, index) => (
-
+
- )),
- ]}
+ />,
+ ...lwOnlyLocSeq.map((component, index) => (
+
+ )),
+ ]}
+ />
+ }
+ />
+ {isActivePipette96ch && isDefaultOffset && (
+
- }
- />
+ )}
+
)
}
+const CONATINER_STYLE = css`
+ flex-direction: ${DIRECTION_COLUMN};
+ gap: ${SPACING.spacing12};
+
+ @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
+ gap: ${SPACING.spacing24};
+ }
+`
+
interface PlaceItemInstructionContentProps extends LPCWizardContentProps {
isLwTiprack: boolean
slotOnlyDisplayLocation: string
diff --git a/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/__tests__/PlaceItemInstruction.test.tsx b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/__tests__/PlaceItemInstruction.test.tsx
new file mode 100644
index 000000000000..0626a724ad1d
--- /dev/null
+++ b/app/src/organisms/LabwarePositionCheck/steps/HandleLabware/EditOffset/PrepareLabware/__tests__/PlaceItemInstruction.test.tsx
@@ -0,0 +1,232 @@
+import { describe, expect, it, beforeEach, vi } from 'vitest'
+import { screen } from '@testing-library/react'
+import { useSelector } from 'react-redux'
+
+import { renderWithProviders } from '/app/__testing-utils__'
+import { i18n } from '/app/i18n'
+import {
+ selectIsSelectedLwTipRack,
+ selectSelectedLwOverview,
+ OFFSET_KIND_DEFAULT,
+ selectLwDisplayName,
+ getFlexSlotNameOnly,
+ selectActivePipetteChannelCount,
+ OFFSET_KIND_LOCATION_SPECIFIC,
+} from '/app/redux/protocol-runs'
+import { PlaceItemInstruction } from '../PlaceItemInstruction'
+
+vi.mock('react-redux', async () => {
+ const actual = await vi.importActual('react-redux')
+ return {
+ ...actual,
+ useSelector: vi.fn(),
+ }
+})
+vi.mock('/app/redux/protocol-runs')
+
+describe('PlaceItemInstruction', () => {
+ const mockRunId = 'mock_run_id'
+ const mockProps = {
+ runId: mockRunId,
+ }
+ const mockSlotLocation = 'Slot C2'
+ const mockLwDisplayName = 'Mock Labware'
+ const mockTipRackDisplayName = 'Mock Tip Rack'
+ const mockLpcState = {
+ protocolData: {},
+ }
+
+ const mockTipRackStackup = {
+ offsetLocationDetails: {
+ kind: OFFSET_KIND_DEFAULT,
+ closestBeneathModuleModel: null,
+ lwModOnlyStackupDetails: [{ kind: 'labware', labwareUri: 'tiprack-uri' }],
+ },
+ }
+
+ const mockLabwareStackup = {
+ offsetLocationDetails: {
+ kind: OFFSET_KIND_DEFAULT,
+ closestBeneathModuleModel: null,
+ lwModOnlyStackupDetails: [{ kind: 'labware', labwareUri: 'labware-uri' }],
+ },
+ }
+
+ const mockLabwareWithModuleStackup = {
+ offsetLocationDetails: {
+ kind: OFFSET_KIND_LOCATION_SPECIFIC,
+ closestBeneathModuleModel: 'temperatureModule',
+ lwModOnlyStackupDetails: [
+ { kind: 'module', moduleModel: 'temperatureModule' },
+ { kind: 'labware', labwareUri: 'labware-uri' },
+ ],
+ },
+ }
+
+ const render = () => {
+ // @ts-expect-error Not all props necessary for testing.
+ return renderWithProviders(, {
+ i18nInstance: i18n,
+ })[0]
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+
+ vi.mocked(getFlexSlotNameOnly).mockReturnValue(mockSlotLocation)
+ vi.mocked(selectLwDisplayName).mockReturnValue(() => mockLwDisplayName)
+
+ vi.mocked(selectIsSelectedLwTipRack).mockImplementation(() => () => false)
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ mockLabwareStackup as any
+ )
+ vi.mocked(selectActivePipetteChannelCount).mockImplementation(() => () => 8)
+
+ vi.mocked(useSelector).mockImplementation(selector => {
+ return selector({
+ protocolRuns: {
+ [mockRunId]: {
+ lpc: mockLpcState,
+ },
+ },
+ })
+ })
+ })
+
+ it('should render prepare labware instruction in slot location', () => {
+ render()
+
+ screen.getByText(`Prepare labware in ${mockSlotLocation}`)
+ })
+
+ it('should render prepare tip rack instruction in slot location', () => {
+ vi.mocked(selectIsSelectedLwTipRack).mockImplementation(() => () => true)
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ mockTipRackStackup as any
+ )
+
+ render()
+
+ screen.getByText(`Prepare tip rack in ${mockSlotLocation}`)
+ })
+
+ it('should show clear deck instruction for default offsets', () => {
+ render()
+
+ screen.getByText(
+ /Clear all deck slots of labware and remove any modules from/
+ )
+ })
+
+ it('should show clear deck but modules instruction for non-default offset with a module', () => {
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ mockLabwareWithModuleStackup as any
+ )
+
+ render()
+
+ screen.getByText(
+ 'Clear all deck slots of labware, leaving modules in place'
+ )
+ })
+
+ it('should show a place labware instruction', () => {
+ render()
+
+ const listItems = screen.getAllByRole('listitem')
+ const labwareItem = listItems.find(
+ li =>
+ li.textContent?.includes('Place a') &&
+ li.textContent.includes(mockLwDisplayName) &&
+ li.textContent.includes(mockSlotLocation)
+ )
+
+ expect(labwareItem).toBeTruthy()
+ })
+
+ it('should show a place tip rack instruction', () => {
+ vi.mocked(selectIsSelectedLwTipRack).mockImplementation(() => () => true)
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ mockTipRackStackup as any
+ )
+ vi.mocked(selectLwDisplayName).mockReturnValue(() => mockTipRackDisplayName)
+
+ render()
+
+ const listItems = screen.getAllByRole('listitem')
+ const tipRackItem = listItems.find(
+ li =>
+ li.textContent?.includes('Place') &&
+ li.textContent.includes('full') &&
+ li.textContent.includes(mockTipRackDisplayName) &&
+ li.textContent.includes(mockSlotLocation)
+ )
+ expect(tipRackItem).toBeTruthy()
+ })
+
+ it('should show inline notification for 96-channel pipette when calibrating a default offset', () => {
+ vi.mocked(selectIsSelectedLwTipRack).mockImplementation(() => () => true)
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ mockTipRackStackup as any
+ )
+ vi.mocked(selectActivePipetteChannelCount).mockImplementation(() => () =>
+ 96
+ )
+
+ render()
+
+ screen.getByText(
+ 'Ensure the tip rack is accurately placed in the slot as outlined above to prevent damage to your labware.'
+ )
+ })
+
+ it('should not show inline notification for other pipettes when calibrating a default offset', () => {
+ render()
+
+ expect(
+ screen.queryByText(
+ 'Ensure the tip rack is accurately placed in the slot as outlined above to prevent damage to your labware.'
+ )
+ ).not.toBeInTheDocument()
+ })
+
+ it('should show next place labware instruction for the second item in a stackup', () => {
+ const multiItemStackup = {
+ offsetLocationDetails: {
+ kind: OFFSET_KIND_DEFAULT,
+ closestBeneathModuleModel: null,
+ lwModOnlyStackupDetails: [
+ { kind: 'labware', labwareUri: 'labware-uri-1' },
+ { kind: 'labware', labwareUri: 'labware-uri-2' },
+ ],
+ },
+ } as any
+
+ vi.mocked(selectSelectedLwOverview).mockImplementation(() => () =>
+ multiItemStackup
+ )
+ vi.mocked(selectLwDisplayName).mockImplementation((runId, uri) => {
+ return () =>
+ uri === 'labware-uri-1' ? 'First Labware' : 'Second Labware'
+ })
+
+ render()
+
+ const listItems = screen.getAllByRole('listitem')
+
+ const firstLabwareItem = listItems.find(
+ li =>
+ li.textContent?.includes('Place a') &&
+ li.textContent?.includes('First Labware')
+ )
+
+ const secondLabwareItem = listItems.find(
+ li =>
+ li.textContent?.includes('Next, place a') &&
+ li.textContent?.includes('Second Labware')
+ )
+
+ expect(firstLabwareItem).toBeTruthy()
+ expect(secondLabwareItem).toBeTruthy()
+ })
+})