name: protocol-designer description: Protocol Designer (PD) application architecture, Redux slices, step/timeline system, domain concepts, and dev workflow. Use when working with files in protocol-designer/ or discussing PD features, steps, timelines, or protocol design.
Protocol Designer — Application-Specific Conventions
General TypeScript, React, styling, testing, import, and tooling conventions are in the opentrons-typescript skill. This file covers only what is unique to the protocol-designer package.
Project Overview
Protocol Designer (PD) is a React + Redux + React Router + TypeScript web app for designing Opentrons liquid-handling protocols. It depends on @opentrons/components, @opentrons/shared-data, and @opentrons/step-generation. It follows an atomic design system.
Redux Architecture
PD uses Redux (legacy createStore) with redux-thunk and reselect. The store shape is BaseState in src/types.ts.
Slices
Each slice directory contains reducers/, actions/, selectors/, and types.ts:
| Slice | Key | Purpose |
|---|---|---|
analytics |
analytics |
Event tracking |
dismiss |
dismiss |
Dismissible UI elements |
feature-flags |
featureFlags |
Feature flags (OT_PD_* env vars) |
file-data |
fileData |
Protocol file data and export |
labware-ingred |
labwareIngred |
Labware, ingredients, deck state |
load-file |
loadFile |
File loading/parsing |
navigation |
navigation |
Navigation state |
step-forms |
stepForms |
Saved step forms, pipettes, modules, labware entities |
tutorial |
tutorial |
Tutorial/hint state |
ui |
ui |
UI state (steps, labware, wells) |
well-selection |
wellSelection |
Well selection state |
Middleware
timelineMiddleware— generates robot state timeline from stepstrackEventMiddleware— analytics event trackingthunk— async action support
Selectors
Use reselect createSelector for all derived state. Per-slice selectors go in selectors/ directories. Cross-slice selectors go in src/top-selectors/.
import { createSelector } from 'reselect'
export const getLabwareNicknamesById = createSelector(
getLabwareEntities,
labwareEntities => mapValues(labwareEntities, e => e.nickname)
)
Thunks
import type { ThunkAction } from '/protocol-designer/types'
export const myThunk = (): ThunkAction<any> => (dispatch, getState) => {
const state = getState()
dispatch({ type: 'MY_ACTION', payload: someSelector(state) })
}
Step & Timeline System
Core Concepts
- Steps: User-created protocol actions (move liquid, set temperature, pause, etc.)
- Timeline: Array of "frames" — each frame has
robotStateandcommands - Step forms: Redux state in
stepForms.savedStepForms(keyed by step ID) - Ordered steps:
stepForms.orderedStepIdsarray
Step Form Flow
- Step form data →
stepFormToArgs()→ command args - Command args →
@opentrons/step-generationcommand creators → protocol commands - Commands →
commandCreatorsTimeline()→ timeline with robot state
Entities
Normalized by ID in Redux:
labwareInvariantProperties— labware on deckpipetteInvariantProperties— pipettes by mountmoduleInvariantProperties— hardware modules
Access via selectors: getLabwareEntities, getPipetteEntities, getModuleEntities.
Pages
Pages live in src/pages/:
Designer/— main protocol design canvas and step editingLanding/— start screenLiquids/— liquid definitions managementProtocolOverview/— protocol summarySettings/— user settingsHardware/— hardware configurationOnboarding/— first-run flow
PD-Specific File Conventions
Feature directories use kebab-case matching the Redux slice name: step-forms/, file-data/, labware-ingred/, well-selection/, etc.
The /protocol-designer/ path alias maps to src/. Use it for imports across feature directories:
import { getFileMetadata } from '/protocol-designer/file-data/selectors'
import type { BaseState } from '/protocol-designer/types'
Internationalization
Uses react-i18next. Translation files in src/assets/localization/.
Common namespaces: 'starting_deck_state', 'protocol_steps', 'shared', 'application'.
const { t } = useTranslation(['protocol_steps', 'shared'])
return (
<>
<span>{t('step_title')}</span>
<span>{t('shared:step_body')}</span>
</>
)
Always use translation keys for user-facing strings — never hardcode display text.
PD Testing
Use renderWithProviders from protocol-designer/src/__testing-utils__/:
import { renderWithProviders } from '/protocol-designer/__testing-utils__'
PD Makefile Targets
Run from the protocol-designer/ directory:
| Target | Description |
|---|---|
make dev |
Start Vite dev server (port 5178) |
make build |
Production build (8GB heap) |
make serve |
Build then preview production assets |
make clean |
Remove dist/ |
make test |
Run PD tests (delegates to root make test-js-protocol-designer) |
make test-cov |
Tests with coverage |
make bundle-analyzer |
Analyze production bundle size |
# Run a specific test file
make test tests="src/components/organisms/__tests__/MyComponent.test.tsx"
# Run vitest directly from monorepo root
pnpm vitest protocol-designer/src/step-forms/
Environment Variables
PD feature flags use OT_PD_* env vars, injected at build time via Vite define. NODE_OPTIONS=--max-old-space-size=8192 is set automatically by the Makefile for dev, build, and serve.