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
2 changes: 1 addition & 1 deletion .cursor/skills/css-modules/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: css-modules
description: CSS Modules conventions, Stylelint rules, design tokens (spacing, colors, typography, border-radius), and patterns for the Opentrons monorepo. Use when working with .module.css files or styling React components.
description: CSS Modules conventions, Stylelint rules, design tokens (spacing, colors, typography, border-radius), and patterns for the Opentrons monorepo. Use when working with .module.css files or styling React components in app/, components/, protocol-visualization/, protocol-designer/, or other JS packages.
---

# CSS Modules — Opentrons Conventions
Expand Down
39 changes: 21 additions & 18 deletions .cursor/skills/opentrons-typescript/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: opentrons-typescript
description: TypeScript conventions, React patterns, testing, styling, and import rules for the Opentrons monorepo JS/TS packages. Use when working with TypeScript or React files in app/, components/, shared-data/, step-generation/, protocol-designer/, opentrons-ai-client/, or other JS/TS packages.
description: TypeScript conventions, React patterns, testing, styling, and import rules for the Opentrons monorepo JS/TS packages. Use when working with TypeScript or React files in app/, components/, shared-data/, step-generation/, protocol-designer/, protocol-visualization/, opentrons-ai-client/, or other JS/TS packages.
---

# Opentrons Monorepo — TypeScript Conventions
Expand All @@ -9,26 +9,27 @@ Node.js, Yarn, Python setup, teardown, and troubleshooting are in the always-app

## Monorepo Structure

Yarn workspaces monorepo with 14 TypeScript packages. No Lerna/Nx/Turbo — uses Yarn Classic workspaces + TypeScript project references.
Yarn workspaces monorepo with 15 TypeScript packages. No Lerna/Nx/Turbo — uses Yarn Classic workspaces + TypeScript project references.

### Packages

| Package | Directory | Type |
| ------------------------------ | ------------------------- | --------------------------- |
| `@opentrons/app` | `app/` | React app |
| `@opentrons/app-shell` | `app-shell/` | Electron shell |
| `@opentrons/app-shell-odd` | `app-shell-odd/` | Electron shell (ODD) |
| `@opentrons/components` | `components/` | React UI components library |
| `@opentrons/api-client` | `api-client/` | Pure TS library |
| `@opentrons/react-api-client` | `react-api-client/` | React hooks library |
| `@opentrons/discovery-client` | `discovery-client/` | Pure TS (Node) |
| `@opentrons/shared-data` | `shared-data/` | Pure TS/JS data library |
| `@opentrons/step-generation` | `step-generation/` | Pure TS library |
| `@opentrons/labware-library` | `labware-library/` | React app |
| `@opentrons/labware-designer` | `labware-designer/` | React app |
| `opentrons-ai-client` | `opentrons-ai-client/` | React app |
| `protocol-designer` | `protocol-designer/` | React app |
| `@opentrons/usb-bridge-client` | `usb-bridge/node-client/` | Pure TS (Node) |
| Package | Directory | Type |
| ----------------------------------- | ------------------------- | --------------------------------- |
| `@opentrons/app` | `app/` | React app |
| `@opentrons/app-shell` | `app-shell/` | Electron shell |
| `@opentrons/app-shell-odd` | `app-shell-odd/` | Electron shell (ODD) |
| `@opentrons/components` | `components/` | React UI components library |
| `@opentrons/api-client` | `api-client/` | Pure TS library |
| `@opentrons/react-api-client` | `react-api-client/` | React hooks library |
| `@opentrons/discovery-client` | `discovery-client/` | Pure TS (Node) |
| `@opentrons/shared-data` | `shared-data/` | Pure TS/JS data library |
| `@opentrons/step-generation` | `step-generation/` | Pure TS library |
| `@opentrons/labware-library` | `labware-library/` | React app |
| `@opentrons/labware-designer` | `labware-designer/` | React app |
| `opentrons-ai-client` | `opentrons-ai-client/` | React app |
| `protocol-designer` | `protocol-designer/` | React app |
| `@opentrons/protocol-visualization` | `protocol-visualization/` | React library (protocol viz, WIP) |
| `@opentrons/usb-bridge-client` | `usb-bridge/node-client/` | Pure TS (Node) |

### Dependency Graph

Expand All @@ -41,6 +42,8 @@ shared-data
├── api-client → react-api-client
└── discovery-client
protocol-visualization (scaffold; depends on components + shared-data + step-generation)
app, protocol-designer, labware-library, opentrons-ai-client (leaf apps)
```

Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/protocol-visualization-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Lint, test, and build @opentrons/protocol-visualization (modeled on components CI).

name: 'Protocol visualization — lint, test, build'

on:
pull_request:
paths:
- '.github/workflows/protocol-visualization-ci.yaml'
- 'Makefile'
- 'package.json'
- 'protocol-visualization/**'
- 'vitest.config.*'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

env:
CI: true

jobs:
protocol-visualization-ci:
name: 'protocol-visualization lint, test, and lib build'
timeout-minutes: 30
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/js/setup
- name: 'typescript project build'
run: make -C protocol-visualization check-ts
- name: 'unit tests with coverage'
run: make -C protocol-visualization test-cov
- name: 'vite lib build'
run: make -C protocol-visualization lib
- name: 'Upload coverage report'
if: github.event_name == 'pull_request'
uses: codecov/codecov-action@v5
with:
files: ./coverage/lcov.info
flags: protocol-visualization
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"labware-library",
"opentrons-ai-client",
"protocol-designer",
"protocol-visualization",
"react-api-client",
"shared-data",
"step-generation",
Expand Down
2 changes: 2 additions & 0 deletions protocol-visualization/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# build output (emitDeclarationOnly + vite lib)
lib/
40 changes: 40 additions & 0 deletions protocol-visualization/CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Conventions for `protocol-visualization`

Follow the same rules as other Opentrons JS/TS packages. Authoritative detail lives in the Cursor skills below; this file is a checklist for contributors.

## TypeScript and React

**Source of truth:** [`.cursor/skills/opentrons-typescript/SKILL.md`](../.cursor/skills/opentrons-typescript/SKILL.md)

Summary:

- Extend [`tsconfig-base.json`](../tsconfig-base.json): strict mode, `composite`, `rootDir: "src"`, `outDir: "lib"`, project **references** to packages you import (`components`, `shared-data`, `step-generation`).
- **No default exports** in source (ESLint `import/no-default-export`). Exceptions: Vite config, Storybook stories.
- **Prettier:** no semicolons, single quotes (double in JSX), trailing commas ES5, print width 80, 2-space indent, LF.
- **Import order** (Prettier `@ianvs/prettier-plugin-sort-imports`): `react` → third party → `@opentrons/*` → relative → `import type` → assets.
- **Cross-package:** this package already lists `@opentrons/components`, `@opentrons/shared-data`, and `@opentrons/step-generation` in `package.json`; keep imports in source limited until UI lands (no imports from `app/`). Tests in `src/__tests__/workspace-dependencies.test.ts` verify those packages resolve.
- **Lodash:** import per function (`import map from 'lodash/map'`), not `import _ from 'lodash'`.
- **Types:** prefer `import type` for type-only imports.

## CSS Modules

**Source of truth:** [`.cursor/skills/css-modules/SKILL.md`](../.cursor/skills/css-modules/SKILL.md)

Summary:

- Filename: **lowercase**, matches component, suffix **`.module.css`** (e.g. `visualizercontainer.module.css`).
- Class names: **snake_case** only (`/^[a-z0-9_]+$/` per Stylelint).
- Import: `import styles from './componentname.module.css'` and use `styles.class_name`.
- **Design tokens:** prefer CSS variables from [`components/src/styles/global.css`](../components/src/styles/global.css) (`--spacing-*`, `--grey-*`, etc.) over raw hex or px where applicable.
- **Conditional classes:** use [`clsx`](https://github.com/lukeed/clsx).

## Monorepo workflow

- **Node:** `>=22.22.0` (see [`.nvmrc`](../.nvmrc)); Yarn Classic 1.22.19.
- From repo root: `make setup-js` if needed; **`make -C protocol-visualization lint`** (same as **`make lint-js lint-css lint-json`** from the repo root) and **`make -C protocol-visualization check-ts`**; scoped tests via `make test-js-protocol-visualization` or `make -C protocol-visualization test`.
- This package’s [`Makefile`](./Makefile) defines `lint` (delegates to the root Makefile) and `check-ts`.

## Lint

- ESLint includes this package via root [`tsconfig-eslint.json`](../tsconfig-eslint.json).
- Before merging, run **`make lint-js lint-css lint-json`** from the monorepo root, or **`make -C protocol-visualization lint`**.
46 changes: 46 additions & 0 deletions protocol-visualization/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# protocol-visualization package makefile

tests ?= src
test_opts ?=

.PHONY: all
all: clean dist

.PHONY: clean
clean:
@:

.PHONY: dist
dist: lib

.PHONY: lib
lib: export NODE_ENV := production
lib:
yarn vite build

.PHONY: build-ts
build-ts:
yarn tsc --build --emitDeclarationOnly

# Same lint surface as monorepo JS checks (see root Makefile).
.PHONY: lint
lint:
$(MAKE) -C .. lint-js lint-css lint-json

.PHONY: check-ts
check-ts:
cd .. && yarn tsc --build protocol-visualization/tsconfig.json

.PHONY: test
test:
$(MAKE) -C .. test-js-protocol-visualization tests="$(tests)" test_opts="$(test_opts)"

cov_opts ?= --coverage=true

.PHONY: test-cov
test-cov:
$(MAKE) -C .. test-js-protocol-visualization tests="$(tests)" test_opts="$(test_opts)" cov_opts="$(cov_opts)"

.PHONY: pack
pack: build-ts lib
yarn pack
79 changes: 79 additions & 0 deletions protocol-visualization/PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# `@opentrons/protocol-visualization` — implementation plan

This document is the working plan for extracting desktop protocol visualization into a standalone library. It lives in this package so reviewers and future PRs share one source of truth.

## Scaffold (initial PR)

- **Package shell** under `protocol-visualization/` with build, types, and a stub entrypoint (see [CHANGES/0001-scaffold-package.md](./CHANGES/0001-scaffold-package.md)).
- **CI** modeled on the components workflow: lint, test, typecheck, and lib build on relevant PRs.

## Goals (visualization work)

- Standalone React library that accepts **protocol analysis** as input and renders the desktop-style visualization experience from `app` (command list, deck, step detail), without modifying `app/`, `components/`, or `step-generation/` until an explicit follow-up chooses to consolidate.
- **Do not modify** those upstream folders during the port; only add and edit files under `protocol-visualization/` (plus normal monorepo root wiring).
- Depend on **`@opentrons/components`**, **`@opentrons/shared-data`**, and (when needed) **`@opentrons/step-generation`** via workspace `link:`.
- No “smart refactor” inside `components` during the port; duplication from `app` into this package is acceptable until a later consolidation.

## Current implementation in `app`

- Main shell: [`app/src/organisms/Desktop/ProtocolVisualization/VisualizerContainer/index.tsx`](../app/src/organisms/Desktop/ProtocolVisualization/VisualizerContainer/index.tsx).
- Subtree: [`app/src/organisms/Desktop/ProtocolVisualization/`](../app/src/organisms/Desktop/ProtocolVisualization/) (~85 files).
- **App coupling** in that container includes Redux (`stepDetailViewer*` actions, analytics), [`useMostRecentCompletedAnalysis`](../app/src/resources/runs), [`getProtocolDisplayName`](../app/src/transformations/protocols), and types from [`protocol-storage`](../app/src/redux/protocol-storage/types.ts).
- **Command list** uses [`AnnotatedSteps`](../app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps/index.tsx), which pulls in [`CommandIcon`](../app/src/molecules/Command/CommandIcon.tsx), [`ProtocolAnalysisErrorModal`](../app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolAnalysisErrorModal.tsx), and more app-only modules.
- Other touches: [`SlotDetailsEmptyState`](../app/src/molecules/SlotDetailsEmptyState/index.tsx), [`getTopPortalEl`](../app/src/App/portal.tsx), [`usePipetteNameSpecs`](../app/src/local-resources/instruments/hooks/usePipetteNameSpecs.ts) (OEM display naming via app settings).

## Approach

Because upstream folders must stay untouched, **copy (port)** the needed UI into `protocol-visualization/src/` and **rewire only inside this package**.

```mermaid
flowchart LR
subgraph consumers [Host web apps]
WebApp[Any React app]
end
subgraph pkg [protocol-visualization]
Entry[Package entry]
Viz[Ported visualizer subtree]
end
WebApp --> Entry
Entry --> Viz
Viz --> Components["@opentrons/components"]
Components --> StepGen["@opentrons/step-generation"]
Viz --> SharedData["@opentrons/shared-data"]
```

### Port checklist

1. **Bulk port** the tree under `app/src/organisms/Desktop/ProtocolVisualization/` into `protocol-visualization/src/` (adjust imports to package-local paths).
2. **Port command-step dependencies**: `AnnotatedSteps` folder, `CommandIcon.tsx`, and related CSS.
3. **Port** `SlotDetailsEmptyState` (component + CSS).
4. **Types**: copy minimal `GroupedCommands` / `LeafNode` / `ParentNode` shapes from `app/src/redux/protocol-storage/types.ts` into this package (no Redux imports from `app`).
5. **Decouple in ported code only**:
- Remove Redux, analytics, and `useMostRecentCompletedAnalysis`; public API takes a single `analysis` prop.
- Replace `getProtocolDisplayName` with a prop or a copied pure helper.
- Replace `usePipetteNameSpecs` with `getPipetteNameSpecs` from `@opentrons/shared-data` for standalone display names.
- Replace `getTopPortalEl` with `document.body` or an optional `portalRoot` prop.
- Replace `ProtocolAnalysisErrorModal` with a small modal built from `@opentrons/components` (no `CodeBlock` / run API hooks).
6. **i18n**: vendor [`app/src/assets/localization/en/protocol_visualization.json`](../app/src/assets/localization/en/protocol_visualization.json) (and `zh` if desired); document `registerProtocolVisualizationI18n(i18n)` or equivalent for hosts.

### Suggested public API (after port)

```ts
// Conceptual; names TBD
export function ProtocolVisualization(props: {
analysis: ProtocolAnalysisOutput
protocolDisplayName?: string
groupedCommands?: GroupedCommands | null
portalRoot?: HTMLElement | null
}): JSX.Element
```

### Non-goals (until explicitly scheduled)

- Wiring the Opentrons `app` to consume this package (requires `app/` edits later).
- Shared abstractions moved into `components/` first.

## Related docs

- [CONVENTIONS.md](./CONVENTIONS.md) — TypeScript and CSS module rules for this repo (with pointers to Cursor skills).
- [CHANGES/0001-scaffold-package.md](./CHANGES/0001-scaffold-package.md) — scaffold PR scope (package + tests + CI workflow).
35 changes: 35 additions & 0 deletions protocol-visualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `@opentrons/protocol-visualization`

Scaffold for a standalone package that will render **protocol analysis** visualizations for any web app. The visualization UI is **not implemented yet**; see [PLAN.md](./PLAN.md) for the roadmap.

Workspace dependencies **`@opentrons/components`**, **`@opentrons/shared-data`**, and **`@opentrons/step-generation`** are declared in `package.json`; [`workspace-dependencies.test.ts`](./src/__tests__/workspace-dependencies.test.ts) asserts they resolve when you run `make -C protocol-visualization test` locally (or in CI).

## Current exports

- **`getProtocolVisualizationPackageName()`** — stable package id string.
- **Type re-exports** — `ProtocolAnalysisOutput`, `CompletedProtocolAnalysis` from `@opentrons/shared-data`.

## Docs

| Doc | Purpose |
| ---------------------------------------------------------------------- | --------------------------------------------- |
| [PLAN.md](./PLAN.md) | Implementation plan and port checklist |
| [CONVENTIONS.md](./CONVENTIONS.md) | TypeScript and CSS module conventions |
| [CHANGES/0001-scaffold-package.md](./CHANGES/0001-scaffold-package.md) | This scaffold PR (includes CI workflow scope) |

## Development

```bash
# from monorepo root
yarn install
make -C protocol-visualization lint
make -C protocol-visualization check-ts
make -C protocol-visualization test
make -C protocol-visualization lib
```

From `protocol-visualization/`, `make lint` runs the root targets **`lint-js`**, **`lint-css`**, and **`lint-json`** (full monorepo JS/CSS/JSON lint).

## CI

Pull requests that touch `protocol-visualization/` run lint, typecheck, tests, and lib build via [`.github/workflows/protocol-visualization-ci.yaml`](../.github/workflows/protocol-visualization-ci.yaml).
36 changes: 36 additions & 0 deletions protocol-visualization/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@opentrons/protocol-visualization",
"version": "0.0.0-dev",
"description": "Standalone React protocol analysis visualization for Opentrons projects",
"source": "src/index.ts",
"types": "lib/index.d.ts",
"main": "lib/index.js",
"module": "lib/index.mjs",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.mjs",
"require": "./lib/index.js"
},
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Opentrons/opentrons.git"
},
"author": "Opentrons Labworks <engineering@opentrons.com> (https://opentrons.com)",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/Opentrons/opentrons/issues"
},
"homepage": "https://github.com/Opentrons/opentrons#readme",
"peerDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0"
},
"dependencies": {
"@opentrons/components": "link:../components",
"@opentrons/shared-data": "link:../shared-data",
"@opentrons/step-generation": "link:../step-generation"
}
}
11 changes: 11 additions & 0 deletions protocol-visualization/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest'

import { getProtocolVisualizationPackageName } from '../index'

describe('@opentrons/protocol-visualization', () => {
it('exposes stable package name', () => {
expect(getProtocolVisualizationPackageName()).toBe(
'@opentrons/protocol-visualization'
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest'

import { COLORS } from '@opentrons/components'
import { CLEAN, EMPTY } from '@opentrons/step-generation'

/**
* Ensures Yarn workspace `link:` dependencies resolve when running tests locally
* (and in CI). The package entrypoint stays a stub until visualization UI lands.
*/
describe('workspace dependencies', () => {
it('resolves @opentrons/components', () => {
expect(COLORS.black90).toMatch(/^#/)
})

it('resolves @opentrons/step-generation', () => {
expect(EMPTY).toBe('EMPTY')
expect(CLEAN).toBe('CLEAN')
})
})
Loading
Loading