From 6d6148722bbc65ee8bd880c88ce5a82d9e27ace1 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sat, 7 Feb 2026 18:22:47 +0100 Subject: [PATCH 01/18] First draft of integration of new Petrinaut into Hash --- apps/hash-frontend/next.config.js | 4 +- apps/hash-frontend/package.json | 4 +- apps/hash-frontend/src/pages/process.page.tsx | 13 +- .../process.page/process-editor-wrapper.tsx | 42 +- .../convert-net-formats.ts | 289 +++++++++++++ .../use-process-save-and-load.tsx | 406 ++---------------- .../update-sub-process-nodes.ts | 189 -------- .../use-persisted-nets.ts | 181 ++------ libs/@hashintel/petrinaut/package.json | 1 + libs/@hashintel/petrinaut/panda.config.ts | 6 + .../petrinaut/src/stubs/node-noop.ts | 36 ++ libs/@hashintel/petrinaut/src/stubs/os.ts | 4 + libs/@hashintel/petrinaut/vite.config.ts | 21 +- yarn.lock | 10 +- 14 files changed, 463 insertions(+), 743 deletions(-) create mode 100644 apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts delete mode 100644 apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts create mode 100644 libs/@hashintel/petrinaut/src/stubs/node-noop.ts create mode 100644 libs/@hashintel/petrinaut/src/stubs/os.ts diff --git a/apps/hash-frontend/next.config.js b/apps/hash-frontend/next.config.js index 834cdfa4084..d6d6bef9f1d 100644 --- a/apps/hash-frontend/next.config.js +++ b/apps/hash-frontend/next.config.js @@ -158,7 +158,9 @@ export default withSentryConfig( "@emotion/server", "@hashintel/block-design-system", "@hashintel/design-system", - "@hashintel/petrinaut-old", + "@hashintel/petrinaut", + "@hashintel/ds-components", + "@hashintel/ds-helpers", "@hashintel/type-editor", "echarts", "zrender", diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index 2e1d13af8ec..3beed7896ed 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -36,7 +36,9 @@ "@glideapps/glide-data-grid": "patch:@glideapps/glide-data-grid@npm%3A6.0.3#~/.yarn/patches/@glideapps-glide-data-grid-npm-6.0.3-f71d586425.patch", "@hashintel/block-design-system": "workspace:*", "@hashintel/design-system": "workspace:*", - "@hashintel/petrinaut-old": "workspace:*", + "@hashintel/ds-components": "workspace:*", + "@hashintel/ds-helpers": "workspace:*", + "@hashintel/petrinaut": "workspace:*", "@hashintel/query-editor": "workspace:*", "@hashintel/type-editor": "workspace:*", "@lit-labs/react": "1.2.1", diff --git a/apps/hash-frontend/src/pages/process.page.tsx b/apps/hash-frontend/src/pages/process.page.tsx index 3828cee0334..610766a97a7 100644 --- a/apps/hash-frontend/src/pages/process.page.tsx +++ b/apps/hash-frontend/src/pages/process.page.tsx @@ -1,6 +1,17 @@ +import dynamic from "next/dynamic"; + import type { NextPageWithLayout } from "../shared/layout"; import { getLayoutWithSidebar } from "../shared/layout"; -import { ProcessEditorWrapper } from "./process.page/process-editor-wrapper"; + +// Petrinaut uses Web Workers, Canvas, Monaco Editor, and the TypeScript compiler +// which all require browser APIs — must not be server-rendered. +const ProcessEditorWrapper = dynamic( + () => + import("./process.page/process-editor-wrapper").then((mod) => ({ + default: mod.ProcessEditorWrapper, + })), + { ssr: false }, +); const ProcessPage: NextPageWithLayout = () => { return ; diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx index 9cba60ef30c..dadd791673d 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper.tsx @@ -1,7 +1,9 @@ +import "@hashintel/petrinaut/dist/main.css"; + import type { EntityId } from "@blockprotocol/type-system"; import { AlertModal } from "@hashintel/design-system"; -import type { PetriNetDefinitionObject } from "@hashintel/petrinaut-old"; -import { defaultTokenTypes, Petrinaut } from "@hashintel/petrinaut-old"; +import type { SDCPN } from "@hashintel/petrinaut"; +import { Petrinaut } from "@hashintel/petrinaut"; import { Box, Stack } from "@mui/material"; import { produce } from "immer"; import { useCallback, useMemo, useState } from "react"; @@ -15,22 +17,17 @@ import { export const ProcessEditorWrapper = () => { const [selectedNetId, setSelectedNetId] = useState(null); const [title, setTitle] = useState("Process"); - const [parentNet, setParentNet] = useState<{ - parentNetId: EntityId; - title: string; - } | null>(null); - - const [petriNetDefinition, setPetriNetDefinition] = - useState({ - arcs: [], - nodes: [], - tokenTypes: defaultTokenTypes, - }); + + const [petriNetDefinition, setPetriNetDefinition] = useState({ + places: [], + transitions: [], + types: [], + differentialEquations: [], + parameters: [], + }); const mutatePetriNetDefinition = useCallback( - ( - mutationFn: (petriNetDefinition: PetriNetDefinitionObject) => undefined, - ) => { + (mutationFn: (petriNetDefinition: SDCPN) => void) => { setPetriNetDefinition((netDefinition) => { const updatedNetDefinition = produce(netDefinition, (draft) => { mutationFn(draft); @@ -55,10 +52,8 @@ export const ProcessEditorWrapper = () => { userEditable, setUserEditable, } = useProcessSaveAndLoad({ - parentNet, petriNet: petriNetDefinition, selectedNetId, - setParentNet, setPetriNet: setPetriNetDefinition, setSelectedNetId, setTitle, @@ -70,17 +65,16 @@ export const ProcessEditorWrapper = () => { petriNetDefinition: newPetriNetDefinition, title: newTitle, }: { - petriNetDefinition: PetriNetDefinitionObject; + petriNetDefinition: SDCPN; title: string; }) => { setPetriNetDefinition(newPetriNetDefinition); setSelectedNetId(null); - setParentNet(null); setUserEditable(true); setTitle(newTitle); }, - [setParentNet, setSelectedNetId, setUserEditable, setTitle], + [setSelectedNetId, setUserEditable, setTitle], ); const loadNetFromId = useCallback( @@ -100,7 +94,7 @@ export const ProcessEditorWrapper = () => { [isDirty, loadPersistedNet, persistedNets], ); - const childProcessOptions = useMemo(() => { + const existingNetOptions = useMemo(() => { return persistedNets .filter((net) => net.userEditable && net.entityId !== selectedNetId) .map((net) => ({ @@ -140,13 +134,13 @@ export const ProcessEditorWrapper = () => { loadNetFromId(id as EntityId)} - parentNet={parentNet} petriNetDefinition={petriNetDefinition} petriNetId={selectedNetId} mutatePetriNetDefinition={mutatePetriNetDefinition} + readonly={!userEditable} setTitle={setTitle} title={title} /> diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts new file mode 100644 index 00000000000..0c6bf0e525b --- /dev/null +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts @@ -0,0 +1,289 @@ +import type { Color, Place, SDCPN, Transition } from "@hashintel/petrinaut"; + +/** + * Old format types from petrinaut-old. + * Defined here to avoid importing from the old package. + */ +type TokenCounts = { + [tokenTypeId: string]: number; +}; + +type ArcData = { + tokenWeights: { + [tokenTypeId: string]: number | undefined; + }; +}; + +type ArcType = { + id: string; + source: string; + target: string; + data?: ArcData; +}; + +type PlaceNodeData = { + label: string; + initialTokenCounts?: TokenCounts; + parentNetNode?: { + id: string; + type: "input" | "output"; + }; + type: "place"; +}; + +type TransitionNodeData = { + conditions?: { + id: string; + name: string; + probability: number; + outputEdgeId: string; + }[]; + label: string; + delay?: number; + description?: string; + childNet?: { + childNetId: string; + childNetTitle: string; + inputPlaceIds: string[]; + outputPlaceIds: string[]; + }; + type: "transition"; +}; + +type NodeType = { + id: string; + type?: string; + position: { x: number; y: number }; + width?: number; + height?: number; + data: PlaceNodeData | TransitionNodeData; +}; + +type TokenType = { + id: string; + name: string; + color: string; +}; + +export type PetriNetDefinitionObject = { + arcs: ArcType[]; + nodes: NodeType[]; + tokenTypes: TokenType[]; +}; + +/** + * Convert old PetriNetDefinitionObject format to new SDCPN format. + * Used when loading persisted data from the graph. + */ +export function convertPetriNetDefinitionObjectToSDCPN( + old: PetriNetDefinitionObject, +): SDCPN { + const places: Place[] = []; + const transitions: Transition[] = []; + + // Separate nodes into places and transitions + for (const node of old.nodes) { + if (node.data.type === "place") { + const placeData = node.data as PlaceNodeData; + places.push({ + id: node.id, + name: placeData.label, + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: node.position.x, + y: node.position.y, + width: node.width, + height: node.height, + }); + } else if (node.data.type === "transition") { + const transitionData = node.data as TransitionNodeData; + + // Find input arcs (arcs where this transition is the target) + const inputArcs = old.arcs + .filter((arc) => arc.target === node.id) + .map((arc) => ({ + placeId: arc.source, + weight: getArcWeight(arc), + })); + + // Find output arcs (arcs where this transition is the source) + const outputArcs = old.arcs + .filter((arc) => arc.source === node.id) + .map((arc) => ({ + placeId: arc.target, + weight: getArcWeight(arc), + })); + + transitions.push({ + id: node.id, + name: transitionData.label, + inputArcs, + outputArcs, + lambdaType: "predicate", + lambdaCode: "return true;", + transitionKernelCode: "return input;", + x: node.position.x, + y: node.position.y, + width: node.width, + height: node.height, + }); + } + } + + // Convert token types to colors + const types: Color[] = old.tokenTypes.map((tokenType) => ({ + id: tokenType.id, + name: tokenType.name, + iconSlug: "circle", + displayColor: tokenType.color, + elements: [], + })); + + return { + places, + transitions, + types, + differentialEquations: [], + parameters: [], + }; +} + +/** + * Convert new SDCPN format to old PetriNetDefinitionObject format. + * Used when saving to the graph to maintain backward compatibility. + */ +export function convertSDCPNToPetriNetDefinitionObject( + sdcpn: SDCPN, +): PetriNetDefinitionObject { + const nodes: NodeType[] = []; + const arcs: ArcType[] = []; + + // Convert places to nodes + for (const place of sdcpn.places) { + nodes.push({ + id: place.id, + type: "place", + position: { x: place.x, y: place.y }, + width: place.width, + height: place.height, + data: { + label: place.name, + type: "place", + }, + }); + } + + // Convert transitions to nodes and extract arcs + let arcIdCounter = 0; + for (const transition of sdcpn.transitions) { + nodes.push({ + id: transition.id, + type: "transition", + position: { x: transition.x, y: transition.y }, + width: transition.width, + height: transition.height, + data: { + label: transition.name, + type: "transition", + }, + }); + + // Convert input arcs (place → transition) + for (const inputArc of transition.inputArcs) { + arcs.push({ + id: `arc-${arcIdCounter++}`, + source: inputArc.placeId, + target: transition.id, + data: { + tokenWeights: createTokenWeights(inputArc.weight, sdcpn.types), + }, + }); + } + + // Convert output arcs (transition → place) + for (const outputArc of transition.outputArcs) { + arcs.push({ + id: `arc-${arcIdCounter++}`, + source: transition.id, + target: outputArc.placeId, + data: { + tokenWeights: createTokenWeights(outputArc.weight, sdcpn.types), + }, + }); + } + } + + // Convert colors to token types + const tokenTypes: TokenType[] = sdcpn.types.map((color) => ({ + id: color.id, + name: color.name, + color: color.displayColor, + })); + + return { + arcs, + nodes, + tokenTypes, + }; +} + +/** + * Helper to get the total weight from an arc's token weights. + * Returns 1 if no weights are defined. + */ +function getArcWeight(arc: ArcType): number { + if (!arc.data?.tokenWeights) { + return 1; + } + const weights = Object.values(arc.data.tokenWeights).filter( + (w): w is number => w !== undefined, + ); + return weights.length > 0 ? Math.max(...weights) : 1; +} + +/** + * Helper to create token weights object from a single weight value. + * Distributes the weight to the first token type if available. + */ +function createTokenWeights( + weight: number, + types: Color[], +): { [tokenTypeId: string]: number | undefined } { + const firstType = types[0]; + if (!firstType) { + return {}; + } + // Apply weight to first token type + return { [firstType.id]: weight }; +} + +/** + * Check if an object is in the old PetriNetDefinitionObject format. + */ +export function isOldFormat(obj: unknown): obj is PetriNetDefinitionObject { + if (typeof obj !== "object" || obj === null) { + return false; + } + const record = obj as Record; + return ( + Array.isArray(record.arcs) && + Array.isArray(record.nodes) && + Array.isArray(record.tokenTypes) + ); +} + +/** + * Check if an object is in the new SDCPN format. + */ +export function isSDCPNFormat(obj: unknown): obj is SDCPN { + if (typeof obj !== "object" || obj === null) { + return false; + } + const record = obj as Record; + return ( + Array.isArray(record.places) && + Array.isArray(record.transitions) && + Array.isArray(record.types) + ); +} diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx index 9534cc36e16..78bdc4dfa30 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx @@ -2,23 +2,15 @@ import { useMutation } from "@apollo/client"; import type { EntityId, PropertyObjectWithMetadata, - PropertyPatchOperation, } from "@blockprotocol/type-system"; -import type { - PetriNetDefinitionObject, - TransitionNodeType, -} from "@hashintel/petrinaut-old"; +import type { SDCPN } from "@hashintel/petrinaut"; import { HashEntity } from "@local/hash-graph-sdk/entity"; import { blockProtocolDataTypes, systemEntityTypes, - systemLinkEntityTypes, systemPropertyTypes, } from "@local/hash-isomorphic-utils/ontology-type-ids"; -import type { - PetriNetPropertiesWithMetadata, - SubProcessOfPropertiesWithMetadata, -} from "@local/hash-isomorphic-utils/system-types/petrinet"; +import type { PetriNetPropertiesWithMetadata } from "@local/hash-isomorphic-utils/system-types/petrinet"; import { type Dispatch, type SetStateAction, @@ -28,66 +20,44 @@ import { } from "react"; import type { - ArchiveEntityMutation, - ArchiveEntityMutationVariables, CreateEntityMutation, CreateEntityMutationVariables, UpdateEntityMutation, UpdateEntityMutationVariables, } from "../../../graphql/api-types.gen"; import { - archiveEntityMutation, createEntityMutation, updateEntityMutation, } from "../../../graphql/queries/knowledge/entity.queries"; import { useActiveWorkspace } from "../../shared/workspace-context"; -import { updateSubProcessDefinitionForParentPlaces } from "./use-process-save-and-load/update-sub-process-nodes"; +import { + convertSDCPNToPetriNetDefinitionObject, + type PetriNetDefinitionObject, +} from "./convert-net-formats"; import { getPersistedNetsFromSubgraph, usePersistedNets, } from "./use-process-save-and-load/use-persisted-nets"; -const areSetsEquivalent = (a: Set, b: Set) => { - if (a.size !== b.size) { - return false; - } - - return a.isSubsetOf(b); -}; - export type PersistedNet = { entityId: EntityId; title: string; - definition: PetriNetDefinitionObject; - parentNet: { parentNetId: EntityId; title: string } | null; - childNetLinksByNodeIdAndChildNetId: { - [nodeId: string]: { - [childNetId: string]: { - linkEntityId: EntityId; - }; - }; - }; + definition: SDCPN; userEditable: boolean; }; type UseProcessSaveAndLoadParams = { - parentNet: { parentNetId: EntityId; title: string } | null; - petriNet: PetriNetDefinitionObject; + petriNet: SDCPN; selectedNetId: EntityId | null; - setParentNet: Dispatch< - SetStateAction<{ parentNetId: EntityId; title: string } | null> - >; - setPetriNet: Dispatch>; + setPetriNet: Dispatch>; setSelectedNetId: Dispatch>; setTitle: Dispatch>; title: string; }; export const useProcessSaveAndLoad = ({ - parentNet, petriNet, selectedNetId, - setParentNet, setSelectedNetId, setPetriNet, setTitle, @@ -120,11 +90,6 @@ export const useProcessSaveAndLoad = ({ UpdateEntityMutationVariables >(updateEntityMutation); - const [archiveEntity] = useMutation< - ArchiveEntityMutation, - ArchiveEntityMutationVariables - >(archiveEntityMutation); - const persistedNet = useMemo(() => { return persistedNets.find((net) => net.entityId === selectedNetId); }, [persistedNets, selectedNetId]); @@ -138,57 +103,66 @@ export const useProcessSaveAndLoad = ({ return true; } - if (parentNet?.parentNetId !== persistedNet.parentNet?.parentNetId) { + if (petriNet.places.length !== persistedNet.definition.places.length) { + return true; + } + + if ( + petriNet.transitions.length !== persistedNet.definition.transitions.length + ) { return true; } - if (petriNet.arcs.length !== persistedNet.definition.arcs.length) { + if (petriNet.types.length !== persistedNet.definition.types.length) { return true; } - if (petriNet.nodes.length !== persistedNet.definition.nodes.length) { + if ( + JSON.stringify(petriNet.places) !== + JSON.stringify(persistedNet.definition.places) + ) { return true; } if ( - petriNet.tokenTypes.length !== persistedNet.definition.tokenTypes.length + JSON.stringify(petriNet.transitions) !== + JSON.stringify(persistedNet.definition.transitions) ) { return true; } if ( - JSON.stringify(petriNet.arcs.map(({ selected: _, ...arc }) => arc)) !== - JSON.stringify(persistedNet.definition.arcs) + JSON.stringify(petriNet.types) !== + JSON.stringify(persistedNet.definition.types) ) { return true; } if ( - JSON.stringify(petriNet.nodes) !== - JSON.stringify(persistedNet.definition.nodes) + JSON.stringify(petriNet.differentialEquations) !== + JSON.stringify(persistedNet.definition.differentialEquations) ) { return true; } if ( - JSON.stringify(petriNet.tokenTypes) !== - JSON.stringify(persistedNet.definition.tokenTypes) + JSON.stringify(petriNet.parameters) !== + JSON.stringify(persistedNet.definition.parameters) ) { return true; } return false; - }, [petriNet, persistedNet, title, parentNet]); + }, [petriNet, persistedNet, title]); const loadPersistedNet = useCallback( (net: PersistedNet) => { setSelectedNetId(net.entityId); - setParentNet(net.parentNet); setPetriNet(net.definition); setTitle(net.title); setUserEditable(net.userEditable); }, - [setParentNet, setPetriNet, setSelectedNetId, setTitle, setUserEditable], + [setPetriNet, setSelectedNetId, setTitle, setUserEditable], ); const refetchPersistedNets = useCallback( @@ -219,6 +193,10 @@ export const useProcessSaveAndLoad = ({ setPersistPending(true); + // Convert SDCPN to old format for persistence (backward compatibility) + const oldFormatDefinition: PetriNetDefinitionObject = + convertSDCPNToPetriNetDefinitionObject(petriNet); + let persistedEntityId = selectedNetId; if (selectedNetId) { @@ -232,17 +210,12 @@ export const useProcessSaveAndLoad = ({ path: [ systemPropertyTypes.definitionObject.propertyTypeBaseUrl, ], + // @ts-expect-error -- PetriNetDefinitionObject not assignable to PropertyWithMetadata property: { metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - arcs: petriNet.arcs, - nodes: petriNet.nodes, - tokenTypes: petriNet.tokenTypes, - } satisfies PetriNetDefinitionObject, + value: oldFormatDefinition, }, }, { @@ -270,11 +243,7 @@ export const useProcessSaveAndLoad = ({ metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - value: { - arcs: petriNet.arcs, - nodes: petriNet.nodes, - tokenTypes: petriNet.tokenTypes, - } satisfies PetriNetDefinitionObject, + value: oldFormatDefinition, }, "https://hash.ai/@h/types/property-type/title/": { metadata: { @@ -300,297 +269,6 @@ export const useProcessSaveAndLoad = ({ throw new Error("Somehow no entityId available after persisting net"); } - /** - * If we have to make changes to places in a linked sub-process, we will need this. - * and it's cheaper to build it once here rather than finding labels repeatedly as and when we need them. - * There shouldn't be many places, so it doesn't really matter if we don't end up needing it. - */ - const placeLabelsById: Record = {}; - for (const node of petriNet.nodes) { - if (node.data.type === "place") { - placeLabelsById[node.id] = node.data.label; - } - } - - /** - * Handle sub-process changes. For any sub-process changed on a transition: - * 1. Archive the previous sub-process, if there was one - * 2. Create a new link to the new sub-process, if there is one - */ - for (const node of petriNet.nodes) { - if (node.data.type !== "transition") { - continue; - } - - const previousTransition = persistedNet?.definition.nodes.find( - (transition): transition is TransitionNodeType => - transition.data.type === "transition" && transition.id === node.id, - ); - - const previousChildNetReference = previousTransition?.data.childNet; - - const oldChildNetId = previousChildNetReference?.childNetId; - const newChildNetId = node.data.childNet?.childNetId; - - const childNetIdentityHasChanged = oldChildNetId !== newChildNetId; - - const existingLinkEntityId = oldChildNetId - ? persistedNet?.childNetLinksByNodeIdAndChildNetId[node.id]?.[ - oldChildNetId - ]?.linkEntityId - : null; - - /** - * Archive the link to the previous sub-process, if it has changed. - */ - if ( - childNetIdentityHasChanged && - existingLinkEntityId && - previousChildNetReference - ) { - await archiveEntity({ - variables: { entityId: existingLinkEntityId }, - }); - - const previousChildNet = persistedNets.find( - (net) => net.entityId === previousChildNetReference.childNetId, - ); - - if (!previousChildNet) { - throw new Error( - `Sub-process ${previousChildNetReference.childNetId} not found`, - ); - } - - /** - * Get the new nodes for the sub-process, with any links to parent places removed. - */ - const updatedChildNetNodes = updateSubProcessDefinitionForParentPlaces({ - subProcessNet: previousChildNet, - inputPlaceLabelById: {}, - outputPlaceLabelById: {}, - }); - - if (updatedChildNetNodes) { - await updateEntity({ - variables: { - entityUpdate: { - entityId: previousChildNet.entityId, - propertyPatches: [ - { - op: "replace", - path: [ - systemPropertyTypes.definitionObject.propertyTypeBaseUrl, - ], - property: { - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - ...previousChildNet.definition, - nodes: updatedChildNetNodes, - } satisfies PetriNetDefinitionObject, - metadata: { - dataTypeId: blockProtocolDataTypes.object.dataTypeId, - }, - }, - }, - ], - }, - }, - }); - } - } - - const childNetDefinition = persistedNets.find( - (net) => net.entityId === newChildNetId, - ); - - if (newChildNetId && !childNetDefinition) { - throw new Error(`Sub-process ${newChildNetId} not found`); - } - - /** - * Create the link from the sub-process to the parent process, if it has changed. - */ - if (childNetIdentityHasChanged && newChildNetId && node.data.childNet) { - await createEntity({ - variables: { - entityTypeIds: [ - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ], - linkData: { - leftEntityId: newChildNetId as EntityId, - rightEntityId: persistedEntityId, - }, - properties: { - value: { - "https://hash.ai/@h/types/property-type/transition-id/": { - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: node.id, - }, - "https://hash.ai/@h/types/property-type/input-place-id/": { - value: node.data.childNet.inputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - "https://hash.ai/@h/types/property-type/output-place-id/": { - value: node.data.childNet.outputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }, - } satisfies SubProcessOfPropertiesWithMetadata as PropertyObjectWithMetadata, - webId: activeWorkspaceWebId, - }, - }); - } - - if (!node.data.childNet || !childNetDefinition) { - /** - * If there is no linked sub-process now, we are done for this transition node. - */ - continue; - } - - /** - * If there is a linked sub-process, we need to check if the selected input or output places for the parent transition node have changed, - * so that we can make sure they are represented in the sub-process. - */ - const childNetInputPlaceIdsChanged = !areSetsEquivalent( - new Set(node.data.childNet.inputPlaceIds), - new Set(previousTransition?.data.childNet?.inputPlaceIds ?? []), - ); - - const childNetOutputPlaceIdsChanged = !areSetsEquivalent( - new Set(node.data.childNet.outputPlaceIds), - new Set( - previousTransition?.data.childNet?.outputPlaceIds ?? [], - ), - ); - - if (childNetInputPlaceIdsChanged || childNetOutputPlaceIdsChanged) { - if (existingLinkEntityId) { - /** - * If we already have a link, we need to update the existing link entity to represent the new input and output places. - */ - const propertyPatches: PropertyPatchOperation[] = []; - - if (childNetInputPlaceIdsChanged) { - propertyPatches.push({ - op: "replace", - path: [systemPropertyTypes.inputPlaceId.propertyTypeBaseUrl], - property: { - value: node.data.childNet.inputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }); - } - - if (childNetOutputPlaceIdsChanged) { - propertyPatches.push({ - op: "replace", - path: [systemPropertyTypes.outputPlaceId.propertyTypeBaseUrl], - property: { - value: node.data.childNet.outputPlaceIds.map((id) => ({ - metadata: { - dataTypeId: blockProtocolDataTypes.text.dataTypeId, - }, - value: id, - })), - }, - }); - } - - await updateEntity({ - variables: { - entityUpdate: { - entityId: existingLinkEntityId, - propertyPatches, - }, - }, - }); - } - - /** - * Now we need to ensure that the input and output places in the parent process are represented in the sub-process. - */ - const inputPlaceLabelById: Record = {}; - const outputPlaceLabelById: Record = {}; - - for (const placeId of node.data.childNet.inputPlaceIds) { - const label = placeLabelsById[placeId]; - - if (!label) { - throw new Error(`Place ${placeId} not found when looking up label`); - } - - inputPlaceLabelById[placeId] = label; - } - - for (const placeId of node.data.childNet.outputPlaceIds) { - const label = placeLabelsById[placeId]; - - if (!label) { - throw new Error(`Place ${placeId} not found when looking up label`); - } - - outputPlaceLabelById[placeId] = label; - } - - const newNodes = updateSubProcessDefinitionForParentPlaces({ - subProcessNet: childNetDefinition, - inputPlaceLabelById, - outputPlaceLabelById, - }); - - if (!newNodes) { - /** - * No changes to the sub-process necessary. - */ - continue; - } - - await updateEntity({ - variables: { - entityUpdate: { - entityId: childNetDefinition.entityId, - propertyPatches: [ - { - op: "replace", - path: [ - systemPropertyTypes.definitionObject.propertyTypeBaseUrl, - ], - property: { - // @ts-expect-error -- incompatibility between JsonValue and some of the Edge types - // @todo fix this - value: { - ...childNetDefinition.definition, - nodes: newNodes, - } satisfies PetriNetDefinitionObject, - metadata: { - dataTypeId: blockProtocolDataTypes.object.dataTypeId, - }, - }, - }, - ], - }, - }, - }); - } - } - await refetchPersistedNets({ updatedEntityId: persistedEntityId }); setSelectedNetId(persistedEntityId); setUserEditable(true); @@ -598,14 +276,8 @@ export const useProcessSaveAndLoad = ({ setPersistPending(false); }, [ activeWorkspaceWebId, - archiveEntity, createEntity, - persistedNet?.definition.nodes, - persistedNet?.childNetLinksByNodeIdAndChildNetId, - persistedNets, - petriNet.arcs, - petriNet.nodes, - petriNet.tokenTypes, + petriNet, refetchPersistedNets, selectedNetId, setSelectedNetId, diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts deleted file mode 100644 index 83abe732bd2..00000000000 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/update-sub-process-nodes.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { NodeType } from "@hashintel/petrinaut-old"; -import { nodeDimensions } from "@hashintel/petrinaut-old"; -import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid"; - -import type { PersistedNet } from "../use-process-save-and-load"; - -/** - * Updates the input and output place nodes in a sub-process net to include new input and output places from the parent transition, - * and remove links to input and output places in the parent which are no longer selected as relevant to the sub-process. - * - * New input places are positioned above the leftmost node(s), and new output places above the rightmost node(s). - * - * @param subProcessNet - The sub-process net to update - * @param inputPlaceLabelById - A map of relevant input place IDs to their labels - * @param outputPlaceLabelById - A map of relevant output place IDs to their labels - * - * @returns The new sub-process nodes, or null if no changes were made - */ -export const updateSubProcessDefinitionForParentPlaces = ({ - subProcessNet, - inputPlaceLabelById, - outputPlaceLabelById, -}: { - subProcessNet: PersistedNet; - inputPlaceLabelById: Record; - outputPlaceLabelById: Record; -}): NodeType[] | null => { - const { nodes } = subProcessNet.definition; - - /** - * We want to find the leftmost and rightmost places, and the minimum y-coordinates for the leftmost and rightmost places, - * so that we can position the new input and output places above the leftmost and rightmost places, respectively. - */ - let minX = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - let minYLeft = Number.POSITIVE_INFINITY; - let minYRight = Number.POSITIVE_INFINITY; - - const newNodes: NodeType[] = []; - - const inputPlaceIds = Object.keys(inputPlaceLabelById); - const outputPlaceIds = Object.keys(outputPlaceLabelById); - - const inputPlaceIdsToAdd = new Set(inputPlaceIds); - const outputPlaceIdsToAdd = new Set(outputPlaceIds); - - let nodesUnlinkedCount = 0; - - for (const node of nodes) { - /** - * Register the node's position so that we can position any new input and output places correctly later. - */ - const x = node.position.x; - const y = node.position.y; - - if (x < minX) { - minX = x; - minYLeft = y; - } else if (x === minX) { - minYLeft = Math.min(minYLeft, y); - } - - if (x > maxX) { - maxX = x; - minYRight = y; - } else if (x === maxX) { - minYRight = Math.min(minYRight, y); - } - - if ( - node.data.type === "place" && - node.data.parentNetNode && - ((node.data.parentNetNode.type === "input" && - !inputPlaceIds.includes(node.data.parentNetNode.id)) || - (node.data.parentNetNode.type === "output" && - !outputPlaceIds.includes(node.data.parentNetNode.id))) - ) { - /** - * This is an input or output place node from the parent which is no longer relevant to the sub-process, - * so we remove the link to the parent process node. - */ - const { parentNetNode: _, ...restData } = node.data; - - newNodes.push({ - ...node, - data: restData, - }); - - nodesUnlinkedCount++; - - continue; - } - - if (node.data.type === "place" && node.data.parentNetNode) { - /** - * Register the fact that we've seen a linked input or output place, so that it doesn't need adding as a new node. - */ - if (node.data.parentNetNode.type === "input") { - inputPlaceIdsToAdd.delete(node.data.parentNetNode.id); - } else { - outputPlaceIdsToAdd.delete(node.data.parentNetNode.id); - } - } - - newNodes.push(node); - } - - if ( - inputPlaceIdsToAdd.size === 0 && - outputPlaceIdsToAdd.size === 0 && - nodesUnlinkedCount === 0 - ) { - /** - * No changes were necessary to the sub-process, so we return null. - */ - return null; - } - - if (minX === Number.POSITIVE_INFINITY) { - minX = 0; - } - if (maxX === Number.NEGATIVE_INFINITY) { - maxX = 300; - } - if (minYLeft === Number.POSITIVE_INFINITY) { - minYLeft = 0; - } - if (minYRight === Number.POSITIVE_INFINITY) { - minYRight = 0; - } - - for (const [index, inputPlaceId] of [...inputPlaceIdsToAdd].entries()) { - const id = generateUuid(); - - const label = inputPlaceLabelById[inputPlaceId]; - - if (!label) { - throw new Error(`Input place label not found for id ${inputPlaceId}`); - } - - newNodes.push({ - id, - type: "place", - position: { - x: minX, - y: minYLeft - (nodeDimensions.place.height + 80) * (index + 1), // stack above leftmost - }, - ...nodeDimensions.place, - data: { - label, - type: "place", - parentNetNode: { - id: inputPlaceId, - type: "input", - }, - }, - }); - } - - for (const [index, outputPlaceId] of [...outputPlaceIdsToAdd].entries()) { - const id = generateUuid(); - - const label = outputPlaceLabelById[outputPlaceId]; - - if (!label) { - throw new Error(`Output place label not found for id ${outputPlaceId}`); - } - - newNodes.push({ - id, - type: "place", - position: { - x: maxX, - y: minYRight - (nodeDimensions.place.height + 80) * (index + 1), // stack above rightmost - }, - ...nodeDimensions.place, - data: { - label, - type: "place", - parentNetNode: { - id: outputPlaceId, - type: "output", - }, - }, - }); - } - - return newNodes; -}; diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts index fe733691f31..85d57e2f63a 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts @@ -1,24 +1,9 @@ import { useQuery } from "@apollo/client"; -import { - getIncomingLinkAndSourceEntities, - getOutgoingLinkAndTargetEntities, - getRoots, -} from "@blockprotocol/graph/stdlib"; -import type { Entity, LinkEntity } from "@blockprotocol/type-system"; -import type { - PetriNetDefinitionObject, - TransitionNodeData, -} from "@hashintel/petrinaut-old"; +import { getRoots } from "@blockprotocol/graph/stdlib"; import { deserializeQueryEntitySubgraphResponse } from "@local/hash-graph-sdk/entity"; import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; -import { - systemEntityTypes, - systemLinkEntityTypes, -} from "@local/hash-isomorphic-utils/ontology-type-ids"; -import type { - PetriNet, - SubProcessOf, -} from "@local/hash-isomorphic-utils/system-types/petrinet"; +import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; +import type { PetriNet } from "@local/hash-isomorphic-utils/system-types/petrinet"; import { useMemo } from "react"; import type { @@ -26,6 +11,12 @@ import type { QueryEntitySubgraphQueryVariables, } from "../../../../graphql/api-types.gen"; import { queryEntitySubgraphQuery } from "../../../../graphql/queries/knowledge/entity.queries"; +import { + convertPetriNetDefinitionObjectToSDCPN, + isOldFormat, + isSDCPNFormat, + type PetriNetDefinitionObject, +} from "../convert-net-formats"; import type { PersistedNet } from "../use-process-save-and-load"; export const getPersistedNetsFromSubgraph = ( @@ -37,142 +28,37 @@ export const getPersistedNetsFromSubgraph = ( const nets = getRoots(subgraph); - const childNetLinksByNodeIdAndChildNetId: PersistedNet["childNetLinksByNodeIdAndChildNetId"] = - {}; - return nets.map((net) => { const netTitle = net.properties["https://hash.ai/@h/types/property-type/title/"]; - const definition = net.properties[ - "https://hash.ai/@h/types/property-type/definition-object/" - ] as PetriNetDefinitionObject; - - const incomingSubProcesses = getIncomingLinkAndSourceEntities( - subgraph, - net.entityId, - ).filter( - ( - linkAndLeftEntity, - ): linkAndLeftEntity is { - linkEntity: LinkEntity[]; - leftEntity: Entity[]; - } => { - const linkEntity = linkAndLeftEntity.linkEntity[0]; - - if (!linkEntity) { - return false; - } - - return linkEntity.metadata.entityTypeIds.includes( - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ); - }, - ); - - const transitionIdToSubprocess = new Map< - string, - TransitionNodeData["childNet"] - >(); - - for (const incomingSubProcess of incomingSubProcesses) { - const subProcessOfLink = incomingSubProcess.linkEntity[0]; - const subProcess = incomingSubProcess.leftEntity[0]; - - if (!subProcessOfLink || !subProcess) { - continue; - } - - childNetLinksByNodeIdAndChildNetId[ - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/transition-id/" - ] - ] = { - [subProcess.entityId]: { - linkEntityId: subProcessOfLink.entityId, - }, - }; + const rawDefinition = + net.properties[ + "https://hash.ai/@h/types/property-type/definition-object/" + ]; - transitionIdToSubprocess.set( - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/transition-id/" - ], - { - childNetTitle: - subProcess.properties[ - "https://hash.ai/@h/types/property-type/title/" - ], - childNetId: subProcess.entityId, - inputPlaceIds: - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/input-place-id/" - ], - outputPlaceIds: - subProcessOfLink.properties[ - "https://hash.ai/@h/types/property-type/output-place-id/" - ], - }, + // Convert from old format to SDCPN if needed + let definition; + if (isOldFormat(rawDefinition)) { + definition = convertPetriNetDefinitionObjectToSDCPN( + rawDefinition as PetriNetDefinitionObject, + ); + } else if (isSDCPNFormat(rawDefinition)) { + definition = rawDefinition; + } else { + // Fallback: treat as old format and attempt conversion + definition = convertPetriNetDefinitionObjectToSDCPN( + rawDefinition as PetriNetDefinitionObject, ); } - const clonedNodes = JSON.parse(JSON.stringify(definition.nodes)); - - const clonedDefinition = { - ...definition, - nodes: clonedNodes, - }; - - for (const node of clonedDefinition.nodes) { - if (node.data.type === "transition") { - const subProcess = transitionIdToSubprocess.get(node.id); - - if (subProcess) { - node.data.subProcess = subProcess; - } - } - } - - const outgoingLinkAndRightEntities = getOutgoingLinkAndTargetEntities( - subgraph, - net.entityId, - ).filter( - ( - linkAndRightEntity, - ): linkAndRightEntity is { - linkEntity: LinkEntity[]; - rightEntity: Entity[]; - } => { - const linkEntity = linkAndRightEntity.linkEntity[0]; - - if (!linkEntity) { - return false; - } - - return linkEntity.metadata.entityTypeIds.includes( - systemLinkEntityTypes.subProcessOf.linkEntityTypeId, - ); - }, - ); - - const parentProcess = outgoingLinkAndRightEntities[0]?.rightEntity[0]; - const userEditable = !!data.queryEntitySubgraph.entityPermissions?.[net.entityId]?.update; return { entityId: net.entityId, title: netTitle, - definition: clonedDefinition, - parentNet: parentProcess - ? { - parentNetId: parentProcess.entityId, - title: - parentProcess.properties[ - "https://hash.ai/@h/types/property-type/title/" - ], - } - : null, - childNetLinksByNodeIdAndChildNetId, + definition, userEditable, }; }); @@ -195,20 +81,7 @@ export const usePersistedNets = () => { }, ], }, - traversalPaths: [ - { - edges: [ - { kind: "has-left-entity", direction: "incoming" }, - { kind: "has-right-entity", direction: "outgoing" }, - ], - }, - { - edges: [ - { kind: "has-right-entity", direction: "incoming" }, - { kind: "has-left-entity", direction: "outgoing" }, - ], - }, - ], + traversalPaths: [], includeDrafts: false, temporalAxes: currentTimeInstantTemporalAxes, includePermissions: true, diff --git a/libs/@hashintel/petrinaut/package.json b/libs/@hashintel/petrinaut/package.json index 9f71018773d..3c3ffeb7ea5 100644 --- a/libs/@hashintel/petrinaut/package.json +++ b/libs/@hashintel/petrinaut/package.json @@ -8,6 +8,7 @@ "url": "git+https://github.com/hashintel/hash.git", "directory": "libs/@hashintel/petrinaut" }, + "style": "dist/main.css", "files": [ "dist", "CHANGELOG.md", diff --git a/libs/@hashintel/petrinaut/panda.config.ts b/libs/@hashintel/petrinaut/panda.config.ts index 40f895ecacc..7fb7eb27fff 100644 --- a/libs/@hashintel/petrinaut/panda.config.ts +++ b/libs/@hashintel/petrinaut/panda.config.ts @@ -4,6 +4,12 @@ export default defineConfig({ // Whether to use css reset preflight: { scope: ".petrinaut-root" }, + // Prefix all utility classes (e.g. `.d_flex` → `.pn_d_flex`) + // prefix: "pn", + + // Scope CSS variables to petrinaut root instead of :root + cssVarRoot: ".petrinaut-root", + // Where to look for css declarations include: ["./src/**/*.{js,jsx,ts,tsx}"], diff --git a/libs/@hashintel/petrinaut/src/stubs/node-noop.ts b/libs/@hashintel/petrinaut/src/stubs/node-noop.ts new file mode 100644 index 00000000000..28149dbd29d --- /dev/null +++ b/libs/@hashintel/petrinaut/src/stubs/node-noop.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ + +/** + * Proxy-based noop stub for Node.js built-in modules (os, fs, path, etc.). + * + * Any property access returns another proxy, any function call returns "". + * This prevents TypeScript compiler and other Node.js-dependent code from + * throwing when their Node.js API calls are unreachable in the browser. + */ + +type Noop = ((...args: unknown[]) => unknown) & Record; + +const handler: ProxyHandler = { + get(_target, prop) { + if (prop === Symbol.toPrimitive) { + return () => ""; + } + if (prop === Symbol.toStringTag) { + return "Module"; + } + if (prop === "__esModule") { + return true; + } + if (prop === "default") { + return noopProxy; + } + return noopProxy; + }, + apply() { + return ""; + }, +}; + +const noopProxy: Noop = new Proxy((() => "") as Noop, handler); + +export default noopProxy; diff --git a/libs/@hashintel/petrinaut/src/stubs/os.ts b/libs/@hashintel/petrinaut/src/stubs/os.ts new file mode 100644 index 00000000000..0aac5fbe1b4 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/stubs/os.ts @@ -0,0 +1,4 @@ +/** Browser stub for Node.js `os` module. */ +export const platform = () => "browser"; +export const EOL = "\n"; +export default { platform, EOL }; diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index a1da6dc726e..9211c00e307 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -13,6 +13,22 @@ export default defineConfig(({ mode }) => { return { root: isLibMode ? undefined : "demo-site", + // Use relative paths for library assets (fixes worker URL in webpack consumers) + // base: isLibMode ? "./" : undefined, + + resolve: { + // Prefer browser exports from packages + // conditions: ["browser", "import", "module"], + alias: { + // Provide browser-safe stubs for Node.js builtins used by TypeScript compiler + os: path.resolve(__dirname, "src/stubs/os.ts"), + fs: path.resolve(__dirname, "src/stubs/node-noop.ts"), + path: path.resolve(__dirname, "src/stubs/node-noop.ts"), + module: path.resolve(__dirname, "src/stubs/node-noop.ts"), + perf_hooks: path.resolve(__dirname, "src/stubs/node-noop.ts"), + }, + }, + build: isLibMode ? // Library build { @@ -40,7 +56,6 @@ export default defineConfig(({ mode }) => { }, sourcemap: true, emptyOutDir: true, - minify: false, } : // Website build { @@ -75,8 +90,10 @@ export default defineConfig(({ mode }) => { define: { __ENVIRONMENT__: JSON.stringify(environment), __SENTRY_DSN__: JSON.stringify(sentryDsn), - // Provide minimal process shim for TypeScript language service in browser + // Stub Node.js globals to enable tree-shaking of Node.js-specific codepaths "process.versions": JSON.stringify({ pnp: undefined }), + "process.platform": JSON.stringify("browser"), + "process.env.NODE_ENV": JSON.stringify("production"), }, optimizeDeps: { include: ["@babel/standalone"], diff --git a/yarn.lock b/yarn.lock index dbde6892e85..60611e72d41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -556,7 +556,9 @@ __metadata: "@graphql-codegen/typescript-operations": "npm:2.5.13" "@hashintel/block-design-system": "workspace:*" "@hashintel/design-system": "workspace:*" - "@hashintel/petrinaut-old": "workspace:*" + "@hashintel/ds-components": "workspace:*" + "@hashintel/ds-helpers": "workspace:*" + "@hashintel/petrinaut": "workspace:*" "@hashintel/query-editor": "workspace:*" "@hashintel/type-editor": "workspace:*" "@lit-labs/react": "npm:1.2.1" @@ -7672,7 +7674,7 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/ds-components@workspace:^, @hashintel/ds-components@workspace:libs/@hashintel/ds-components": +"@hashintel/ds-components@workspace:*, @hashintel/ds-components@workspace:^, @hashintel/ds-components@workspace:libs/@hashintel/ds-components": version: 0.0.0-use.local resolution: "@hashintel/ds-components@workspace:libs/@hashintel/ds-components" dependencies: @@ -7768,7 +7770,7 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/petrinaut-old@workspace:*, @hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old": +"@hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old": version: 0.0.0-use.local resolution: "@hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old" dependencies: @@ -7801,7 +7803,7 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/petrinaut@workspace:libs/@hashintel/petrinaut": +"@hashintel/petrinaut@workspace:*, @hashintel/petrinaut@workspace:libs/@hashintel/petrinaut": version: 0.0.0-use.local resolution: "@hashintel/petrinaut@workspace:libs/@hashintel/petrinaut" dependencies: From 64a843df2ed37e2f109d63f4b4abbea589171a1d Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sat, 7 Feb 2026 18:35:50 +0100 Subject: [PATCH 02/18] Remove @hashintel/petrinaut-old package from workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration to @hashintel/petrinaut is complete — nothing depends on the old package anymore. Remove it entirely and clean up the changeset ignore list. Co-Authored-By: Claude Opus 4.6 --- .changeset/config.json | 1 - libs/@hashintel/petrinaut-old/.npmignore | 6 - .../petrinaut-old/LICENSE-APACHE.md | 189 ----- libs/@hashintel/petrinaut-old/LICENSE-MIT.md | 21 - libs/@hashintel/petrinaut-old/LICENSE.md | 3 - libs/@hashintel/petrinaut-old/README.md | 39 - libs/@hashintel/petrinaut-old/dev/dev-app.tsx | 12 - .../petrinaut-old/dev/dev-wrapper.tsx | 166 ---- libs/@hashintel/petrinaut-old/dev/index.html | 31 - .../petrinaut-old/dev/tsconfig.json | 8 - .../petrinaut-old/dev/vite.config.ts | 11 - .../@hashintel/petrinaut-old/eslint.config.js | 58 -- libs/@hashintel/petrinaut-old/package.json | 58 -- libs/@hashintel/petrinaut-old/src/main.ts | 1 - .../petrinaut-old/src/petrinaut.tsx | 742 ---------------- .../src/petrinaut/apply-node-changes.ts | 118 --- .../src/petrinaut/arc-editor.tsx | 118 --- .../petrinaut-old/src/petrinaut/arc.tsx | 188 ----- .../petrinaut-old/src/petrinaut/create-arc.ts | 31 - .../src/petrinaut/editor-context.tsx | 117 --- .../petrinaut-old/src/petrinaut/examples.ts | 340 -------- .../src/petrinaut/generate-uuid.ts | 3 - .../petrinaut-old/src/petrinaut/log-pane.tsx | 104 --- .../src/petrinaut/net-selector.tsx | 91 -- .../src/petrinaut/place-editor.tsx | 128 --- .../src/petrinaut/place-node.tsx | 106 --- .../petrinaut-old/src/petrinaut/sidebar.tsx | 93 -- .../src/petrinaut/simulation-context.tsx | 341 -------- .../src/petrinaut/simulation-controls.tsx | 176 ---- .../petrinaut-old/src/petrinaut/styling.ts | 53 -- .../src/petrinaut/title-and-net-select.tsx | 80 -- .../src/petrinaut/token-types.tsx | 62 -- .../token-types/default-token-types.ts | 5 - .../token-types/token-type-editor.tsx | 226 ----- .../src/petrinaut/transition-editor.tsx | 795 ------------------ .../src/petrinaut/transition-node.tsx | 79 -- .../petrinaut-old/src/petrinaut/types.ts | 99 --- .../src/petrinaut/use-convert-to-pnml.ts | 182 ---- .../src/petrinaut/use-layout-graph.ts | 117 --- .../src/petrinaut/use-load-from-pnml.ts | 195 ----- .../petrinaut-old/theme-override.d.ts | 1 - libs/@hashintel/petrinaut-old/tsconfig.json | 10 - yarn.lock | 63 -- 43 files changed, 5267 deletions(-) delete mode 100644 libs/@hashintel/petrinaut-old/.npmignore delete mode 100644 libs/@hashintel/petrinaut-old/LICENSE-APACHE.md delete mode 100644 libs/@hashintel/petrinaut-old/LICENSE-MIT.md delete mode 100644 libs/@hashintel/petrinaut-old/LICENSE.md delete mode 100644 libs/@hashintel/petrinaut-old/README.md delete mode 100644 libs/@hashintel/petrinaut-old/dev/dev-app.tsx delete mode 100644 libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx delete mode 100644 libs/@hashintel/petrinaut-old/dev/index.html delete mode 100644 libs/@hashintel/petrinaut-old/dev/tsconfig.json delete mode 100644 libs/@hashintel/petrinaut-old/dev/vite.config.ts delete mode 100644 libs/@hashintel/petrinaut-old/eslint.config.js delete mode 100644 libs/@hashintel/petrinaut-old/package.json delete mode 100644 libs/@hashintel/petrinaut-old/src/main.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/types.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts delete mode 100644 libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts delete mode 100644 libs/@hashintel/petrinaut-old/theme-override.d.ts delete mode 100644 libs/@hashintel/petrinaut-old/tsconfig.json diff --git a/.changeset/config.json b/.changeset/config.json index a5694e506e9..24c91b31c43 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,7 +11,6 @@ "@local/*", "@rust/*", "@tests/*", - "@hashintel/petrinaut-old", "@hashintel/block-design-system", "@hashintel/design-system", "@hashintel/query-editor", diff --git a/libs/@hashintel/petrinaut-old/.npmignore b/libs/@hashintel/petrinaut-old/.npmignore deleted file mode 100644 index eb376535262..00000000000 --- a/libs/@hashintel/petrinaut-old/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -## Ignore all files (but still allow sub-folder scanning) -* -!*/ - -## Allow-list for files and folders -!dist/** diff --git a/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md b/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md deleted file mode 100644 index 4b43328a923..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE-APACHE.md +++ /dev/null @@ -1,189 +0,0 @@ -# Apache License - -_Version 2.0, January 2004_ -_<>_ - -### Terms and Conditions for use, reproduction, and distribution - -#### 1. Definitions - -“License” shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -“Licensor” shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -“Legal Entity” shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, “control” means **(i)** the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the -outstanding shares, or **(iii)** beneficial ownership of such entity. - -“You” (or “Your”) shall mean an individual or Legal Entity exercising -permissions granted by this License. - -“Source” form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -“Object” form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -“Work” shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -“Derivative Works” shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -“Contribution” shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -“submitted” means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as “Not a Contribution.” - -“Contributor” shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -#### 2. Grant of Copyright License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -#### 3. Grant of Patent License - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -#### 4. Redistribution - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -- **(a)** You must give any other recipients of the Work or Derivative Works a copy of - this License; and -- **(b)** You must cause any modified files to carry prominent notices stating that You - changed the files; and -- **(c)** You must retain, in the Source form of any Derivative Works that You distribute, - all copyright, patent, trademark, and attribution notices from the Source form - of the Work, excluding those notices that do not pertain to any part of the - Derivative Works; and -- **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any - Derivative Works that You distribute must include a readable copy of the - attribution notices contained within such NOTICE file, excluding those notices - that do not pertain to any part of the Derivative Works, in at least one of the - following places: within a NOTICE text file distributed as part of the - Derivative Works; within the Source form or documentation, if provided along - with the Derivative Works; or, within a display generated by the Derivative - Works, if and wherever such third-party notices normally appear. The contents of - the NOTICE file are for informational purposes only and do not modify the - License. You may add Your own attribution notices within Derivative Works that - You distribute, alongside or as an addendum to the NOTICE text from the Work, - provided that such additional attribution notices cannot be construed as - modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -#### 5. Submission of Contributions - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -#### 6. Trademarks - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -#### 7. Disclaimer of Warranty - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -#### 8. Limitation of Liability - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -#### 9. Accepting Warranty or Additional Liability - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -_END OF TERMS AND CONDITIONS_ - -### APPENDIX: Apply the Apache License to a specific file - -To apply the Apache License to an individual file, attach the following notice. -The text should be enclosed in the appropriate comment syntax for the file -format. - - Copyright © 2025–, HASH - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/libs/@hashintel/petrinaut-old/LICENSE-MIT.md b/libs/@hashintel/petrinaut-old/LICENSE-MIT.md deleted file mode 100644 index d85585ee20d..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE-MIT.md +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright © 2025–, HASH - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/@hashintel/petrinaut-old/LICENSE.md b/libs/@hashintel/petrinaut-old/LICENSE.md deleted file mode 100644 index 6dad94c0e5a..00000000000 --- a/libs/@hashintel/petrinaut-old/LICENSE.md +++ /dev/null @@ -1,3 +0,0 @@ -# License - -Licensed under either of the [Apache License, Version 2.0](LICENSE-APACHE.md) or [MIT license](LICENSE-MIT.md) at your option. diff --git a/libs/@hashintel/petrinaut-old/README.md b/libs/@hashintel/petrinaut-old/README.md deleted file mode 100644 index b54cba89386..00000000000 --- a/libs/@hashintel/petrinaut-old/README.md +++ /dev/null @@ -1,39 +0,0 @@ - -# Petrinaut - -A component for editing [**Petri nets**](https://en.wikipedia.org/wiki/Petri_net). - -Currently **under development** and not ready for usage. - -## Development Mode - -For development and testing, you can use the included dev mode: - -```bash -yarn dev -``` - -This will start a development server with a fully functional Petrinaut editor that uses local storage to persist created nets. - -## Usage - -The component currently depends on a specific MUI theme and any consuming application should wrap it as follows: - -```tsx -import { CacheProvider } from "@emotion/react"; -import { ThemeProvider } from "@mui/material/styles"; -import { createEmotionCache, theme } from "@hashintel/design-system/theme"; - -const emotionCache = createEmotionCache(); - -const App = () => { - - return ( - - - - - - ) -} -``` diff --git a/libs/@hashintel/petrinaut-old/dev/dev-app.tsx b/libs/@hashintel/petrinaut-old/dev/dev-app.tsx deleted file mode 100644 index 67a9ace90cf..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/dev-app.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; - -import { DevWrapper } from "./dev-wrapper"; - -const root = createRoot(document.getElementById("root") as HTMLElement); - -root.render( - - - , -); diff --git a/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx b/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx deleted file mode 100644 index c2355b34e82..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/dev-wrapper.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { CacheProvider } from "@emotion/react"; -import { - createEmotionCache, - fluidTypographyStyles, - theme, -} from "@hashintel/design-system/theme"; -import { useLocalStorage } from "@mantine/hooks"; -import { ThemeProvider } from "@mui/material"; -import { produce } from "immer"; -import { useCallback, useEffect, useMemo, useState } from "react"; - -import { Petrinaut } from "../src/petrinaut"; -import type { - MinimalNetMetadata, - PetriNetDefinitionObject, -} from "../src/petrinaut/types"; - -const emotionCache = createEmotionCache(); - -type StoredNet = { - id: string; - title: string; - petriNetDefinition: PetriNetDefinitionObject; - createdAt: string; -}; - -export const DevWrapper = () => { - const [storedNets, setStoredNets] = useLocalStorage({ - key: "petrinaut-dev-nets", - defaultValue: [], - getInitialValueInEffect: false, - }); - - const [currentNetId, setCurrentNetId] = useState(null); - - const currentNet = useMemo(() => { - if (!currentNetId) { - return null; - } - return storedNets.find((net) => net.id === currentNetId) ?? null; - }, [currentNetId, storedNets]); - - const existingNets: MinimalNetMetadata[] = useMemo(() => { - return storedNets - .filter((net) => net.id !== currentNetId) - .map((net) => ({ - netId: net.id, - title: net.title, - })); - }, [currentNetId, storedNets]); - - const createNewNet = useCallback( - (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => { - const newNet: StoredNet = { - id: `net-${Date.now()}`, - title: params.title, - petriNetDefinition: params.petriNetDefinition, - createdAt: new Date().toISOString(), - }; - - setStoredNets((prev) => [...prev, newNet]); - setCurrentNetId(newNet.id); - }, - [setStoredNets], - ); - - const loadPetriNet = useCallback((petriNetId: string) => { - setCurrentNetId(petriNetId); - }, []); - - const setTitle = useCallback( - (title: string) => { - if (!currentNetId) { - return; - } - - setStoredNets((prev) => - prev.map((net) => (net.id === currentNetId ? { ...net, title } : net)), - ); - }, - [currentNetId, setStoredNets], - ); - - const mutatePetriNetDefinition = useCallback( - (definitionMutationFn: (draft: PetriNetDefinitionObject) => void) => { - if (!currentNetId) { - return; - } - - setStoredNets((prev) => - prev.map((net) => { - if (net.id === currentNetId) { - const newDefinition = produce( - net.petriNetDefinition, - definitionMutationFn, - ); - return { ...net, petriNetDefinition: newDefinition }; - } - return net; - }), - ); - }, - [currentNetId, setStoredNets], - ); - - // Initialize with a default net if none exists - useEffect(() => { - if (!storedNets[0]) { - createNewNet({ - petriNetDefinition: { - arcs: [], - nodes: [], - tokenTypes: [], - }, - title: "New Process", - }); - } else if (!currentNetId) { - setCurrentNetId(storedNets[0].id); - } - }, [storedNets.length, currentNetId, createNewNet, storedNets]); - - if (!currentNet) { - return ( - - -
-

Petrinaut Dev Mode

-

No net selected. Create a new net to get started.

-
-
-
- ); - } - - return ( - - - -
- -
-
-
- ); -}; diff --git a/libs/@hashintel/petrinaut-old/dev/index.html b/libs/@hashintel/petrinaut-old/dev/index.html deleted file mode 100644 index d95b457c1eb..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Petrinaut Dev Mode - - - -
- - - diff --git a/libs/@hashintel/petrinaut-old/dev/tsconfig.json b/libs/@hashintel/petrinaut-old/dev/tsconfig.json deleted file mode 100644 index 42a456f130a..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "moduleResolution": "bundler", - "jsx": "react-jsx" - }, - "include": ["./*.tsx", "./*.ts"] -} diff --git a/libs/@hashintel/petrinaut-old/dev/vite.config.ts b/libs/@hashintel/petrinaut-old/dev/vite.config.ts deleted file mode 100644 index 8681959d659..00000000000 --- a/libs/@hashintel/petrinaut-old/dev/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -export default defineConfig({ - mode: "development", - plugins: [react()], - root: "dev", - server: { - open: true, - }, -}); diff --git a/libs/@hashintel/petrinaut-old/eslint.config.js b/libs/@hashintel/petrinaut-old/eslint.config.js deleted file mode 100644 index c64e56f76ea..00000000000 --- a/libs/@hashintel/petrinaut-old/eslint.config.js +++ /dev/null @@ -1,58 +0,0 @@ -import { createBase } from "@local/eslint/deprecated"; - -export default [ - ...createBase(import.meta.dirname), - { - languageOptions: { - parserOptions: { - projectService: { - allowDefaultProject: [ - "assets.d.ts", - "panda.config.ts", - "postcss.config.cjs", - ], - }, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - files: ["dev/**/*"], - languageOptions: { - parserOptions: { - projectService: { - defaultProject: "./dev/tsconfig.json", - }, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - files: ["dev/*.tsx"], - rules: { - "import/no-extraneous-dependencies": ["error", { devDependencies: true }], - }, - }, - { - rules: { - // Disabled because React Compiler handles optimization automatically - "react/jsx-no-bind": "off", - "no-restricted-imports": [ - "error", - { - patterns: [ - { - group: ["@mui/material/*"], - message: "Please import from @mui/material instead", - }, - { - group: ["@local/*"], - message: - "You cannot use unpublished local packages in a published package.", - }, - ], - }, - ], - }, - }, -]; diff --git a/libs/@hashintel/petrinaut-old/package.json b/libs/@hashintel/petrinaut-old/package.json deleted file mode 100644 index 49167ff30f1..00000000000 --- a/libs/@hashintel/petrinaut-old/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@hashintel/petrinaut-old", - "version": "0.0.5", - "private": true, - "description": "A visual editor for Petri nets", - "license": "(MIT OR Apache-2.0)", - "repository": { - "type": "git", - "url": "https://github.com/hashintel/hash.git", - "directory": "libs/@hashintel/petrinaut-old" - }, - "type": "module", - "sideEffects": false, - "main": "src/main.ts", - "types": "src/main.ts", - "scripts": { - "dev": "vite --config dev/vite.config.ts", - "fix:eslint": "eslint --fix .", - "lint:eslint": "eslint --report-unused-disable-directives .", - "lint:tsc": "tsc", - "prepublishOnly": "PACKAGE_DIR=$(pwd) yarn workspace @local/repo-chores exe scripts/prepublish.ts", - "postpublish": "PACKAGE_DIR=$(pwd) yarn workspace @local/repo-chores exe scripts/postpublish.ts" - }, - "dependencies": { - "@emotion/react": "11.14.0", - "@fortawesome/free-solid-svg-icons": "6.7.2", - "@handsontable/react": "16.1.1", - "@hashintel/block-design-system": "workspace:*", - "@hashintel/design-system": "workspace:*", - "@mantine/hooks": "8.3.5", - "@mui/material": "5.18.0", - "@mui/system": "5.18.0", - "elkjs": "0.11.0", - "reactflow": "11.11.4", - "uuid": "13.0.0", - "zustand": "5.0.8" - }, - "devDependencies": { - "@emotion/react": "11.14.0", - "@local/eslint": "workspace:*", - "@mui/material": "5.18.0", - "@mui/system": "5.18.0", - "@types/react": "19.2.7", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "5.0.4", - "eslint": "9.39.2", - "immer": "10.1.3", - "react": "19.2.3", - "react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "7.1.11", - "web-worker": "1.4.1" - }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - } -} diff --git a/libs/@hashintel/petrinaut-old/src/main.ts b/libs/@hashintel/petrinaut-old/src/main.ts deleted file mode 100644 index fa5b406eb94..00000000000 --- a/libs/@hashintel/petrinaut-old/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./petrinaut"; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut.tsx deleted file mode 100644 index 67c510c6637..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut.tsx +++ /dev/null @@ -1,742 +0,0 @@ -import "reactflow/dist/style.css"; - -import { Box, Button, Stack } from "@mui/material"; -import type { DragEvent } from "react"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import type { - Connection, - Node, - NodeChange, - ReactFlowInstance, -} from "reactflow"; -import ReactFlow, { - Background, - ConnectionLineType, - ReactFlowProvider, - useReactFlow, -} from "reactflow"; - -import { applyNodeChanges } from "./petrinaut/apply-node-changes"; -import { Arc } from "./petrinaut/arc"; -import { ArcEditor } from "./petrinaut/arc-editor"; -import { createArc } from "./petrinaut/create-arc"; -import { - EditorContextProvider, - type MutatePetriNetDefinition, - useEditorContext, -} from "./petrinaut/editor-context"; -import { exampleCPN } from "./petrinaut/examples"; -import { generateUuid } from "./petrinaut/generate-uuid"; -import { LogPane } from "./petrinaut/log-pane"; -import { PlaceEditor } from "./petrinaut/place-editor"; -import { PlaceNode } from "./petrinaut/place-node"; -import { Sidebar } from "./petrinaut/sidebar"; -import { SimulationContextProvider } from "./petrinaut/simulation-context"; -import { SimulationControls } from "./petrinaut/simulation-controls"; -import { nodeDimensions } from "./petrinaut/styling"; -import { TitleAndNetSelect } from "./petrinaut/title-and-net-select"; -import { TokenTypes } from "./petrinaut/token-types"; -import { defaultTokenTypes } from "./petrinaut/token-types/default-token-types"; -import { TransitionEditor } from "./petrinaut/transition-editor"; -import { TransitionNode } from "./petrinaut/transition-node"; -import type { - ArcData, - ArcType, - MinimalNetMetadata, - NodeData, - NodeType, - ParentNet, - PetriNetDefinitionObject, - PlaceNodeData, - PlaceNodeType, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -} from "./petrinaut/types"; -import { useConvertToPnml } from "./petrinaut/use-convert-to-pnml"; -import { useLoadFromPnml } from "./petrinaut/use-load-from-pnml"; - -export type { - ArcData, - ArcType, - MinimalNetMetadata, - NodeData, - NodeType, - ParentNet, - PetriNetDefinitionObject, - PlaceNodeData, - PlaceNodeType, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -}; - -export { nodeDimensions }; - -export { defaultTokenTypes }; - -export { NetSelector } from "./petrinaut/net-selector"; - -type DraggingStateByNodeId = Record< - string, - { dragging: boolean; position: { x: number; y: number } } ->; - -const PetrinautInner = ({ - hideNetManagementControls, -}: { hideNetManagementControls: boolean }) => { - const canvasContainer = useRef(null); - - const [reactFlowInstance, setReactFlowInstance] = useState | null>(null); - - const { createNewNet, petriNetDefinition, mutatePetriNetDefinition, title } = - useEditorContext(); - - const [selectedPlacePosition, setSelectedPlacePosition] = useState<{ - x: number; - y: number; - } | null>(null); - - const [selectedPlaceId, setSelectedPlaceId] = useState(null); - const [selectedTransition, setSelectedTransition] = useState( - null, - ); - - const [selectedArc, setSelectedArc] = useState< - (ArcType & { position: { x: number; y: number } }) | null - >(null); - - /** - * While a node is being dragged, we don't want to keep reporting position changes to the consumer, - * but we need to track the fact it's being dragged and where it is currently for reactflow to use. - * This state tracks that information. - */ - const [draggingStateByNodeId, setDraggingStateByNodeId] = - useState({}); - - useEffect(() => { - setDraggingStateByNodeId({}); - }, [petriNetDefinition.nodes]); - - const nodeTypes = useMemo( - () => ({ - place: PlaceNode, - transition: TransitionNode, - }), - [], - ); - - const edgeTypes = useMemo( - () => ({ - default: Arc, - }), - [], - ); - - const { setViewport } = useReactFlow(); - - useEffect(() => { - setViewport({ x: 0, y: 0, zoom: 1 }); - }, [setViewport]); - - const onNodesChange = useCallback( - (changes: NodeChange[]) => { - applyNodeChanges({ - changes, - draggingStateByNodeId, - mutatePetriNetDefinition, - setDraggingStateByNodeId, - }); - }, - [draggingStateByNodeId, mutatePetriNetDefinition], - ); - - const onEdgesChange = useCallback(() => { - /** - * There are no edge changes we need to process at the moment: - * - We add arcs in onConnect, we won't don't process 'add' - * - We don't allow removing arcs at the moment - * - We handle selection in separate state when an edge is clicked - * - Unclear what 'reset' is supposed to do - */ - }, []); - - const isValidConnection = useCallback( - (connection: Connection) => { - const sourceNode = petriNetDefinition.nodes.find( - (node) => node.id === connection.source, - ); - const targetNode = petriNetDefinition.nodes.find( - (node) => node.id === connection.target, - ); - - if (!sourceNode || !targetNode) { - return false; - } - - // Places can only connect to transitions and vice versa - return sourceNode.type !== targetNode.type; - }, - [petriNetDefinition.nodes], - ); - - const onConnect = useCallback( - (connection: Connection) => { - if (isValidConnection(connection)) { - const newArc: ArcType = { - ...connection, - id: `arc__${connection.source}-${connection.target}`, - source: connection.source ?? "", - target: connection.target ?? "", - type: "default", - data: { - tokenWeights: { [petriNetDefinition.tokenTypes[0]!.id]: 1 }, - }, - interactionWidth: 8, - }; - - const addedArc = createArc(newArc, petriNetDefinition.arcs); - - if (!addedArc) { - return; - } - - mutatePetriNetDefinition((existingNet) => { - existingNet.arcs.push(addedArc); - }); - } - }, - [ - petriNetDefinition.arcs, - isValidConnection, - petriNetDefinition.tokenTypes, - mutatePetriNetDefinition, - ], - ); - - const onInit = useCallback((instance: ReactFlowInstance) => { - setReactFlowInstance(instance); - }, []); - - const onDragOver = useCallback((event: DragEvent) => { - event.preventDefault(); - // eslint-disable-next-line no-param-reassign - event.dataTransfer.dropEffect = "move"; - }, []); - - const onDrop = useCallback( - (event: DragEvent) => { - event.preventDefault(); - - if (!reactFlowInstance || !canvasContainer.current) { - return; - } - - const reactFlowBounds = canvasContainer.current.getBoundingClientRect(); - const nodeType = event.dataTransfer.getData("application/reactflow") as - | "place" - | "transition"; - - const { width, height } = nodeDimensions[nodeType]; - - const position = reactFlowInstance.project({ - x: event.clientX - reactFlowBounds.left - width / 2, - y: event.clientY - reactFlowBounds.top - height / 2, - }); - - const newNode: NodeType = { - id: `${nodeType}__${generateUuid()}`, - type: nodeType, - position, - ...nodeDimensions[nodeType], - data: { - label: `${nodeType} ${petriNetDefinition.nodes.length + 1}`, - ...(nodeType === "place" - ? { - type: "place", - tokenCounts: {}, - } - : { type: "transition" }), - }, - }; - - mutatePetriNetDefinition((existingNet) => { - existingNet.nodes.push(newNode); - }); - }, - [reactFlowInstance, petriNetDefinition.nodes, mutatePetriNetDefinition], - ); - - const onNodeClick = useCallback( - (event: React.MouseEvent, node: Node) => { - if (selectedPlaceId && selectedPlaceId === node.id) { - return; - } - - setSelectedPlaceId(null); - setSelectedPlacePosition(null); - setSelectedArc(null); - setSelectedTransition(null); - - if (node.type === "place") { - setSelectedPlaceId(node.id); - setSelectedPlacePosition({ x: event.clientX, y: event.clientY }); - } else if (node.type === "transition") { - setSelectedTransition(node.id); - } - }, - [selectedPlaceId], - ); - - const handleUpdateInitialTokens = useCallback( - (nodeId: string, initialTokenCounts: TokenCounts) => { - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - if (node.id === nodeId && node.data.type === "place") { - // @todo don't overwrite the whole object, update what has changed only - node.data.initialTokenCounts = initialTokenCounts; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handleUpdateNodeLabel = useCallback( - (nodeId: string, label: string) => { - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - if (node.id === nodeId) { - node.data.label = label; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const onEdgeClick = useCallback((event: React.MouseEvent, edge: ArcType) => { - event.stopPropagation(); - - setSelectedArc({ - ...edge, - position: { x: event.clientX, y: event.clientY }, - }); - }, []); - - const handleUpdateEdgeWeight = useCallback( - ( - edgeId: string, - tokenWeights: { [tokenTypeId: string]: number | undefined }, - ) => { - mutatePetriNetDefinition((existingNet) => { - for (const arc of existingNet.arcs) { - if (arc.id === edgeId) { - // @todo don't overwrite the whole object, update what has changed only - arc.data ??= { tokenWeights: {} }; - arc.data.tokenWeights = tokenWeights; - return; - } - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handlePaneClick = useCallback(() => { - setSelectedPlaceId(null); - setSelectedTransition(null); - setSelectedArc(null); - }, []); - - const handleUpdateTransition = useCallback( - ( - transitionId: string, - transitionData: Omit, - ) => { - mutatePetriNetDefinition((existingNet) => { - const transitionNode = existingNet.nodes.find( - (node): node is TransitionNodeType => - node.id === transitionId && node.type === "transition", - ); - - if (!transitionNode) { - throw new Error(`Transition node with id ${transitionId} not found`); - } - - if (transitionData.label !== transitionNode.data.label) { - transitionNode.data.label = transitionData.label; - } - - if (transitionData.description !== transitionNode.data.description) { - transitionNode.data.description = transitionData.description; - } - - if (transitionData.delay !== transitionNode.data.delay) { - transitionNode.data.delay = transitionData.delay; - } - - if (transitionData.childNet !== transitionNode.data.childNet) { - // @todo check equality of nested fields - transitionNode.data.childNet = transitionData.childNet; - } - - if (transitionData.conditions !== transitionNode.data.conditions) { - // @todo check equality of nested fields - transitionNode.data.conditions = transitionData.conditions; - } - }); - }, - [mutatePetriNetDefinition], - ); - - const handleLoadExample = useCallback(() => { - createNewNet({ - petriNetDefinition: { - arcs: exampleCPN.arcs, - nodes: exampleCPN.nodes, - tokenTypes: exampleCPN.tokenTypes, - }, - title: exampleCPN.title, - }); - }, [createNewNet]); - - const convertToPnml = useConvertToPnml({ - petriNet: petriNetDefinition, - title, - }); - - const handleExport = () => { - const pnml = convertToPnml(); - - const blob = new Blob([pnml], { type: "application/xml" }); - - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = "process.pnml"; - - document.body.appendChild(a); - a.click(); - - setTimeout(() => { - document.body.removeChild(a); - URL.revokeObjectURL(url); - }, 0); - }; - - const loadFromPnml = useLoadFromPnml({ - createNewNet, - }); - - const handleLoadFromPnml = useCallback( - (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) { - return; - } - - const reader = new FileReader(); - reader.onload = (readerEvent) => { - const contents = readerEvent.target?.result; - if (typeof contents === "string") { - loadFromPnml(contents); - } - }; - reader.readAsText(file); - }, - [loadFromPnml], - ); - - const fileInputRef = useRef(null); - - const handleImportClick = () => { - fileInputRef.current?.click(); - }; - - const selectedPlace = useMemo(() => { - if (!selectedPlaceId) { - return null; - } - - const place = petriNetDefinition.nodes.find( - (node): node is PlaceNodeType => - node.id === selectedPlaceId && node.data.type === "place", - ); - - if (!place) { - throw new Error(`Cannot find place with id ${selectedPlaceId}`); - } - - return place; - }, [petriNetDefinition.nodes, selectedPlaceId]); - - const nodesForReactFlow = useMemo(() => { - return petriNetDefinition.nodes.map((node) => { - const draggingState = draggingStateByNodeId[node.id]; - - return { - ...node, - // Fold in dragging state (the consumer isn't aware of it, as it's a transient property) - dragging: draggingState?.dragging ?? false, - position: draggingState?.dragging - ? draggingState.position - : node.position, - }; - }); - }, [petriNetDefinition.nodes, draggingStateByNodeId]); - - return ( - - {!hideNetManagementControls && } - - - - - - - - - - - {selectedTransition && ( - setSelectedTransition(null)} - transitionId={selectedTransition} - outgoingEdges={petriNetDefinition.arcs - .filter((edge) => edge.source === selectedTransition) - .map((edge) => { - const targetNode = petriNetDefinition.nodes.find( - (node) => node.id === edge.target, - ); - return { - id: edge.id, - source: edge.source, - target: edge.target, - targetLabel: targetNode?.data.label ?? "Unknown", - tokenWeights: edge.data?.tokenWeights ?? {}, - }; - })} - onUpdateTransition={handleUpdateTransition} - /> - )} - - {selectedPlace && ( - setSelectedPlaceId(null)} - onUpdateInitialTokens={handleUpdateInitialTokens} - onUpdateNodeLabel={handleUpdateNodeLabel} - /> - )} - - {selectedArc && ( - setSelectedArc(null)} - onUpdateWeights={handleUpdateEdgeWeight} - /> - )} - - - - - - - - {!hideNetManagementControls && ( - - - - - - - - )} - - - - ); -}; - -export type PetrinautProps = { - /** - * Nets other than this one which are available for selection, e.g. to switch to or to link from a transition. - */ - existingNets: MinimalNetMetadata[]; - /** - * Create a new net and load it into the editor. - */ - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - /** - * Whether to hide controls relating to net loading, creation and title setting. - */ - hideNetManagementControls: boolean; - /** - * Minimal metadata on the net which this net is a child of, if any. - */ - parentNet: ParentNet | null; - /** - * The ID of the net which is currently loaded. - */ - petriNetId: string | null; - /** - * The definition of the net which is currently loaded. - */ - petriNetDefinition: PetriNetDefinitionObject; - /** - * Update the definition of the net which is currently loaded, by mutation. - * - * Should not return anything – the consumer of Petrinaut must pass mutationFn into an appropriate helper - * which does something with the mutated object, e.g. `immer`'s `produce` or `@automerge/react`'s `changeDoc`. - * - * @example - * mutatePetriNetDefinition((petriNetDefinition) => { - * petriNetDefinition.nodes.push({ - * id: "new-node", - * type: "place", - * position: { x: 0, y: 0 }, - * }); - * }); - * - * @see https://immerjs.github.io/immer - * @see https://automerge.org - */ - mutatePetriNetDefinition: MutatePetriNetDefinition; - /** - * Load a new net by id. - */ - loadPetriNet: (petriNetId: string) => void; - /** - * Set the title of the net which is currently loaded. - */ - setTitle: (title: string) => void; - /** - * The title of the net which is currently loaded. - */ - title: string; -}; - -export const Petrinaut = ({ - createNewNet, - existingNets, - hideNetManagementControls, - parentNet, - petriNetId, - petriNetDefinition, - mutatePetriNetDefinition, - loadPetriNet, - setTitle, - title, -}: PetrinautProps) => { - return ( - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts deleted file mode 100644 index 294619fc66e..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/apply-node-changes.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Dispatch, SetStateAction } from "react"; -import type { NodeAddChange, NodeChange } from "reactflow"; - -import type { MutatePetriNetDefinition } from "./editor-context"; - -export type DraggingStateByNodeId = Record< - string, - { dragging: boolean; position: { x: number; y: number } } ->; - -/** - * A variant of reactflow's applyChange which mutates the petri net definition instead of creating a new node or edge array. - * - * @see https://github.com/xyflow/xyflow/blob/04055c9625cbd92cf83a2f4c340d6fae5199bfa3/packages/react/src/utils/changes.ts#L107 - */ -export const applyNodeChanges = ({ - changes, - draggingStateByNodeId, - mutatePetriNetDefinition, - setDraggingStateByNodeId, -}: { - changes: NodeChange[]; - draggingStateByNodeId: DraggingStateByNodeId; - mutatePetriNetDefinition: MutatePetriNetDefinition; - setDraggingStateByNodeId: Dispatch>; -}) => { - const changesByNodeId: Record = {}; - const addChanges: NodeAddChange[] = []; - - for (const change of changes) { - if ( - // We add nodes in onDrop, we won't handle these kind of changes - change.type === "add" || - // unclear what reset is supposed to do, it's not handled in reactflow's applyChange implementation - change.type === "reset" || - // We handle selection in separate state ourselves - change.type === "select" || - // We don't allow resizing at the moment - change.type === "dimensions" - ) { - continue; - } - - if (change.type === "position") { - if (change.dragging) { - setDraggingStateByNodeId((existing) => ({ - ...existing, - [change.id]: { - dragging: true, - position: change.position ?? { x: 0, y: 0 }, - }, - })); - } else { - const lastPosition = draggingStateByNodeId[change.id]?.position; - - if (!lastPosition) { - // we've had a dragging: false with no preceding dragging: true, so the node has not been dragged anywhere. - continue; - } - - /** - * When dragging stops, we receive a change event with 'dragging: false' but no position. - * We use the last position we received to report the change to the consumer. - */ - changesByNodeId[change.id] ??= []; - changesByNodeId[change.id]!.push({ - type: "position", - id: change.id, - position: lastPosition, - }); - - setDraggingStateByNodeId((existing) => ({ - ...existing, - [change.id]: { - dragging: false, - position: lastPosition, - }, - })); - } - } - } - - if (addChanges.length === 0 && Object.keys(changesByNodeId).length === 0) { - return; - } - - mutatePetriNetDefinition((existingNet) => { - for (const node of existingNet.nodes) { - const changesForNode: NodeChange[] = changesByNodeId[node.id] ?? []; - - for (const change of changesForNode) { - if (change.type === "position") { - if (change.position) { - if (node.position.x !== change.position.x) { - node.position.x = change.position.x; - } - if (node.position.y !== change.position.y) { - node.position.y = change.position.y; - } - } - - if (change.positionAbsolute) { - if (node.positionAbsolute?.x !== change.positionAbsolute.x) { - node.positionAbsolute ??= { x: 0, y: 0 }; - node.positionAbsolute.x = change.positionAbsolute.x; - } - if (node.positionAbsolute.y !== change.positionAbsolute.y) { - node.positionAbsolute ??= { x: 0, y: 0 }; - node.positionAbsolute.y = change.positionAbsolute.y; - } - } - } - } - } - - existingNet.nodes.push(...addChanges.map((change) => change.item)); - }); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx deleted file mode 100644 index 336774a3c9c..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/arc-editor.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { Box, Card, Stack, Typography } from "@mui/material"; -import { useEffect, useMemo, useState } from "react"; -import { useReactFlow } from "reactflow"; - -import { useEditorContext } from "./editor-context"; - -interface ArcMenuProps { - arcId: string; - tokenWeights: { - [tokenTypeId: string]: number | undefined; - }; - position: { x: number; y: number }; - onClose: () => void; - onUpdateWeights: ( - arcId: string, - tokenWeights: { [tokenTypeId: string]: number | undefined }, - ) => void; -} - -export const ArcEditor = ({ - arcId, - tokenWeights, - position, - onClose: _onClose, - onUpdateWeights, -}: ArcMenuProps) => { - const [localWeights, setLocalWeights] = useState<{ - [tokenTypeId: string]: number | undefined; - }>(tokenWeights); - - const { getNodes, getEdges } = useReactFlow(); - - const { petriNetDefinition } = useEditorContext(); - - const direction = useMemo(() => { - const arc = getEdges().find((edge) => edge.id === arcId); - - const targetNode = getNodes().find((node) => node.id === arc?.target); - - if (!targetNode) { - return "in"; - } - - return targetNode.type === "transition" ? "in" : "out"; - }, [arcId, getEdges, getNodes]); - - useEffect(() => { - setLocalWeights(tokenWeights); - }, [tokenWeights]); - - const handleWeightChange = (tokenTypeId: string, value: string) => { - const parsedValue = Number.parseInt(value, 10); - const newWeight = Math.max(0, parsedValue || 0); - - const newWeights = { - ...localWeights, - [tokenTypeId]: newWeight, - }; - setLocalWeights(newWeights); - onUpdateWeights(arcId, newWeights); - }; - - return ( - - - - {direction === "in" ? "Tokens Required" : "Tokens Produced"} - - - {petriNetDefinition.tokenTypes.map((tokenType) => ( - - - - - {tokenType.name} - - - - handleWeightChange(tokenType.id, event.target.value) - } - size="small" - sx={{ width: 80 }} - /> - - ))} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx deleted file mode 100644 index 77255a7cee6..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/arc.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { getBezierPath, type Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { generateUuid } from "./generate-uuid"; -import { useSimulationContext } from "./simulation-context"; -import type { TokenType } from "./types"; - -type AnimatingToken = { - id: string; - tokenTypeId: string; -}; - -export const Arc = ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - data, -}: { - id: string; - sourceX: number; - sourceY: number; - targetX: number; - targetY: number; - sourcePosition: Position; - targetPosition: Position; - data?: { - tokenWeights: { - [tokenTypeId: string]: number; - }; - }; -}) => { - const { petriNetDefinition } = useEditorContext(); - - const { simulationSpeed } = useSimulationContext(); - - const [animatingTokens, setAnimatingTokens] = useState([]); - const [arcPath, labelX, labelY] = getBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - }); - - const addAnimatingToken = useCallback((tokenTypeId: string) => { - const newToken: AnimatingToken = { - id: generateUuid(), - tokenTypeId, - }; - - setAnimatingTokens((current) => [...current, newToken]); - }, []); - - /** - * Handle the event fired from SimulationContext to animate a token along the arcs of enabled transitions. - */ - useEffect(() => { - const handleTransitionFired = ( - event: CustomEvent<{ - arcId: string; - tokenTypeId: string; - }>, - ) => { - const { arcId, tokenTypeId } = event.detail; - if (arcId === id) { - addAnimatingToken(tokenTypeId); - } - }; - - window.addEventListener( - "animateTokenAlongArc", - handleTransitionFired as EventListener, - ); - - return () => { - window.removeEventListener( - "animateTokenAlongArc", - handleTransitionFired as EventListener, - ); - }; - }, [id, addAnimatingToken]); - - return ( - <> - - - {animatingTokens.map((token) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt: TokenType) => tt.id === token.tokenTypeId, - ); - return ( - - - - ); - })} - - - {/* Show tokens required or produced */} - {Object.entries(data?.tokenWeights ?? {}) - .filter(([_, weight]) => weight > 0) - .map(([tokenTypeId, weight], index, nonZeroWeights) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt: TokenType) => tt.id === tokenTypeId, - ); - - if (!tokenType) { - return null; - } - - const yOffset = (index - (nonZeroWeights.length - 1) / 2) * 20; - - return ( - - - - {weight} - - - ); - })} - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts deleted file mode 100644 index 4542a97f424..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/create-arc.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ArcType } from "./types"; - -const connectionExists = (arc: ArcType, arcs: ArcType[]) => { - return arcs.some( - (el) => - el.source === arc.source && - el.target === arc.target && - (el.sourceHandle === arc.sourceHandle || - (!el.sourceHandle && !arc.sourceHandle)) && - (el.targetHandle === arc.targetHandle || - (!el.targetHandle && !arc.targetHandle)), - ); -}; - -/** - * This is a variant of reactflow's `addEdge`. We use this so we can control adding the arc to the definition by mutation. - * addEdge otherwise creates a new array with the edge added via concat. - * - * We don't need a lot of the checking in there because we require a properly constructed ArcType to be passed in. - * - * @see https://github.com/xyflow/xyflow/blob/04055c9625cbd92cf83a2f4c340d6fae5199bfa3/packages/system/src/utils/edges/general.ts#L113 - * - * @returns ArcType if the arc shouod be added, null if it should not (because it already exists) - */ -export const createArc = (arc: ArcType, arcs: ArcType[]): ArcType | null => { - if (connectionExists(arc, arcs)) { - return null; - } - - return arc; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx deleted file mode 100644 index a70182c29f9..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/editor-context.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - createContext, - type Dispatch, - type SetStateAction, - useContext, - useMemo, - useState, -} from "react"; - -import type { - MinimalNetMetadata, - ParentNet, - PetriNetDefinitionObject, -} from "./types"; - -export type MutatePetriNetDefinition = ( - mutationFn: (petriNetDefinition: PetriNetDefinitionObject) => undefined, -) => void; - -type EditorContextValue = { - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - existingNets: MinimalNetMetadata[]; - loadPetriNet: (petriNetId: string) => void; - parentNet: ParentNet | null; - petriNetId: string | null; - petriNetDefinition: PetriNetDefinitionObject; - readonly: boolean; - setParentNet: Dispatch>; - mutatePetriNetDefinition: MutatePetriNetDefinition; - setTitle: (title: string) => void; - title: string; -}; - -const EditorContext = createContext(undefined); - -type EditorContextProviderProps = { - children: React.ReactNode; - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; - existingNets: MinimalNetMetadata[]; - parentNet: ParentNet | null; - petriNetId: string | null; - petriNetDefinition: PetriNetDefinitionObject; - mutatePetriNetDefinition: MutatePetriNetDefinition; - loadPetriNet: (petriNetId: string) => void; - readonly: boolean; - setTitle: (title: string) => void; - title: string; -}; - -export const EditorContextProvider = ({ - children, - createNewNet, - existingNets, - parentNet: parentNetFromProps, - petriNetId, - petriNetDefinition, - readonly, - loadPetriNet, - mutatePetriNetDefinition, - setTitle, - title, -}: EditorContextProviderProps) => { - const [parentNet, setParentNet] = useState( - parentNetFromProps, - ); - - const value: EditorContextValue = useMemo( - () => ({ - createNewNet, - existingNets, - loadPetriNet, - parentNet, - petriNetId, - petriNetDefinition, - readonly, - setParentNet, - mutatePetriNetDefinition, - setTitle, - title, - }), - [ - createNewNet, - existingNets, - loadPetriNet, - mutatePetriNetDefinition, - parentNet, - petriNetId, - petriNetDefinition, - readonly, - setParentNet, - setTitle, - title, - ], - ); - - return ( - {children} - ); -}; - -export const useEditorContext = () => { - const context = useContext(EditorContext); - - if (!context) { - throw new Error( - "useEditorContext must be used within an EditorContextProvider", - ); - } - - return context; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts deleted file mode 100644 index 3d7e3183713..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/examples.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { nodeDimensions } from "./styling"; -import type { ArcType, NodeType, TokenType } from "./types"; - -export const exampleCPN = { - title: "Drug Production", - tokenTypes: [ - { id: "precursor_a", name: "Precursor A", color: "#3498db" }, - { id: "precursor_b", name: "Precursor B", color: "#f1c40f" }, - { id: "drug", name: "Drug", color: "#2ecc71" }, - { id: "failed_drug", name: "Failed Drug", color: "#e74c3c" }, - ] satisfies TokenType[], - nodes: [ - { - id: "place_0", - type: "place", - position: { x: 20, y: 120 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Plant A Supply", - initialTokenCounts: { - precursor_a: 10, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_1", - type: "place", - position: { x: 20, y: 600 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Plant B Supply", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 10, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_2", - type: "place", - position: { x: 300, y: 300 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Manufacturing Plant", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_3", - type: "place", - position: { x: 700, y: 350 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "QA Queue", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_4", - type: "place", - position: { x: 1100, y: 600 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Disposal", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_5", - type: "place", - position: { x: 1000, y: 200 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Dispatch", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_6", - type: "place", - position: { x: 1300, y: 380 }, - ...nodeDimensions.place, - data: { - type: "place", - label: "Hospital", - initialTokenCounts: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_0", - type: "transition", - position: { x: 100, y: 400 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Deliver to Plant", - description: "Transport precursors to manufacturing plant", - delay: 2, - }, - }, - { - id: "transition_1", - type: "transition", - position: { x: 490, y: 350 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Manufacture", - description: "Combine precursors to create drug", - delay: 3, - }, - }, - { - id: "transition_2", - type: "transition", - position: { x: 870, y: 400 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Quality Check", - delay: 2, - description: "Quality assurance with conditional outputs", - conditions: [ - { - id: "condition-pass", - name: "Pass", - probability: 80, - outputEdgeId: "transition_2-place_5", - }, - { - id: "condition-fail", - name: "Fail", - probability: 20, - outputEdgeId: "transition_2-place_4", - }, - ], - }, - }, - { - id: "transition_3", - type: "transition", - position: { x: 1150, y: 280 }, - ...nodeDimensions.transition, - data: { - type: "transition", - label: "Ship", - description: "Ship drugs to hospital", - delay: 3, - }, - }, - ] satisfies NodeType[], - arcs: [ - { - id: "place_0-transition_0", - source: "place_0", - target: "transition_0", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 0, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_1-transition_0", - source: "place_1", - target: "transition_0", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_0-place_2", - source: "transition_0", - target: "place_2", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "place_2-transition_1", - source: "place_2", - target: "transition_1", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 1, - precursor_b: 1, - drug: 0, - failed_drug: 0, - }, - }, - }, - { - id: "transition_1-place_3", - source: "transition_1", - target: "place_3", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "place_3-transition_2", - source: "place_3", - target: "transition_2", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_2-place_5", - source: "transition_2", - target: "place_5", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_2-place_4", - source: "transition_2", - target: "place_4", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 0, - failed_drug: 1, - }, - }, - }, - { - id: "place_5-transition_3", - source: "place_5", - target: "transition_3", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - { - id: "transition_3-place_6", - source: "transition_3", - target: "place_6", - type: "default", - interactionWidth: 8, - data: { - tokenWeights: { - precursor_a: 0, - precursor_b: 0, - drug: 1, - failed_drug: 0, - }, - }, - }, - ] satisfies ArcType[], -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts deleted file mode 100644 index 29c60045acd..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/generate-uuid.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { v4 as uuidv4 } from "uuid"; - -export const generateUuid = () => uuidv4(); diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx deleted file mode 100644 index d845ea4a2c8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/log-pane.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { CaretDownSolidIcon, IconButton } from "@hashintel/design-system"; -import { Box, Collapse, Stack, Typography } from "@mui/material"; -import { useEffect, useRef, useState } from "react"; - -import { useSimulationContext } from "./simulation-context"; - -export const LogPane = () => { - const { simulationLogs } = useSimulationContext(); - const logsContainerRef = useRef(null); - const [expanded, setExpanded] = useState(false); - - useEffect(() => { - if (expanded && logsContainerRef.current) { - logsContainerRef.current.scrollTo({ - top: logsContainerRef.current.scrollHeight, - behavior: "smooth", - }); - } - }, [simulationLogs, expanded]); - - if (simulationLogs.length === 0) { - return null; - } - - return ( - - setExpanded(!expanded)} - sx={({ transitions }) => ({ - display: "flex", - justifyContent: "space-between", - p: 0, - pr: 1.5, - alignItems: "center", - "&:hover": { - backgroundColor: "transparent", - "& svg, & span": { color: ({ palette }) => palette.common.black }, - }, - "& svg": { - transform: expanded ? "none" : "rotate(-90deg)", - transition: transitions.create(["transform", "color"], { - duration: 200, - }), - position: "relative", - top: expanded ? -2 : 0, - }, - width: "100%", - })} - > - ({ - color: ({ palette }) => palette.gray[70], - transition: transitions.create("color", { - duration: 200, - }), - })} - > - Simulation Logs - - palette.gray[50], - }} - /> - - - - {simulationLogs.map((log) => ( - - {log.text} - - ))} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx deleted file mode 100644 index 516fefcdf82..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/net-selector.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Autocomplete, CaretDownSolidIcon } from "@hashintel/design-system"; -import { MenuItem, outlinedInputClasses } from "@mui/material"; -import { useMemo, useRef } from "react"; - -import type { MinimalNetMetadata } from "./types"; - -export const NetSelector = ({ - disabledOptions, - onSelect, - options, - placeholder, - value, -}: { - disabledOptions?: string[]; - onSelect: (option: MinimalNetMetadata) => void; - options: MinimalNetMetadata[]; - placeholder?: string; - value: string | null; -}) => { - const selectedOption = useMemo(() => { - return options.find((option) => option.netId === value); - }, [options, value]); - - const inputRef = useRef(null); - - if (options.length === 0) { - return null; - } - - return ( - - autoFocus={false} - componentsProps={{ - paper: { - sx: { - p: 0, - maxWidth: "90vw", - minWidth: "100%", - width: "fit-content", - }, - }, - }} - disableCloseOnSelect={false} - disabled={options.length === 0} - getOptionDisabled={(option) => - !!option && !!disabledOptions?.includes(option.netId) - } - getOptionLabel={(option) => option?.title ?? ""} - inputHeight={40} - inputProps={{ - endAdornment: , - placeholder: placeholder ?? "Select a net to load", - sx: () => ({ - [`&.${outlinedInputClasses.root}`]: { - py: 0.3, - px: "8px !important", - - input: { - fontSize: 14, - }, - }, - }), - }} - inputRef={inputRef} - isOptionEqualToValue={(option, selectedValue) => - option?.netId === selectedValue?.netId - } - renderOption={(props, data) => ( - - {data?.title ?? ""} - - )} - ListboxProps={{ - sx: { - maxHeight: 240, - }, - }} - onChange={(_event, option) => { - if (!option) { - return; - } - - onSelect(option); - inputRef.current?.blur(); - }} - options={options} - sx={{ maxWidth: 300 }} - value={selectedOption} - /> - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx deleted file mode 100644 index 6c91c0b197f..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/place-editor.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { Box, Button, Card, Stack, Typography } from "@mui/material"; - -import type { PlaceNodeType, TokenCounts, TokenType } from "./types"; - -export type PlaceEditorProps = { - selectedPlace: PlaceNodeType; - tokenTypes: TokenType[]; - onClose: () => void; - onUpdateInitialTokens: (nodeId: string, tokenCounts: TokenCounts) => void; - onUpdateNodeLabel: (nodeId: string, label: string) => void; - position: { x: number; y: number }; -}; - -export const PlaceEditor = ({ - position, - selectedPlace, - tokenTypes, - onClose, - onUpdateInitialTokens, - onUpdateNodeLabel, -}: PlaceEditorProps) => { - const { data, id: placeId } = selectedPlace; - const { label: nodeName, initialTokenCounts } = data; - - const handleTokenCountChange = (tokenTypeId: string, value: string) => { - const numValue = parseInt(value, 10); - const newCount = Number.isNaN(numValue) ? 0 : Math.max(0, numValue); - - const newTokenCounts = { - ...initialTokenCounts, - [tokenTypeId]: newCount, - }; - - onUpdateInitialTokens(placeId, newTokenCounts); - }; - - const handleNodeNameChange = (event: React.ChangeEvent) => { - const newName = event.target.value; - onUpdateNodeLabel(placeId, newName); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - (event.target as HTMLInputElement).blur(); - } - }; - - return ( - - - - - Name - - - - - {tokenTypes.length > 0 && ( - - - Initial Token Counts - - - {tokenTypes.map((tokenType) => ( - - - - - {tokenType.name} - - - - handleTokenCountChange(tokenType.id, event.target.value) - } - size="small" - sx={{ width: 80 }} - /> - - ))} - - - )} - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx deleted file mode 100644 index 0a3af62eed8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/place-node.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { IconDiagramRegular } from "@hashintel/design-system"; -import { Box, Tooltip } from "@mui/material"; -import { Handle, type NodeProps, Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { useSimulationContext } from "./simulation-context"; -import { handleStyling, nodeDimensions, placeStyling } from "./styling"; -import type { PlaceNodeData } from "./types"; - -const tokenSize = 22; -const halfTokenSize = tokenSize / 2; - -const getTokenPosition = (index: number) => { - // Calculate the angle for this token (in radians), starting from top (-π/2), distributed to fit 16 tokens evenly around the place border - const angle = (index * 2 * Math.PI) / 16 - Math.PI / 2; - - const radius = nodeDimensions.place.width / 2; - - return { - left: `calc(50% + ${radius * Math.cos(angle)}px - ${halfTokenSize}px)`, - top: `calc(50% + ${radius * Math.sin(angle)}px - ${halfTokenSize}px + 1px)`, - }; -}; - -export const PlaceNode = ({ - data, - id, - isConnectable, -}: NodeProps) => { - const { placeMarkingsById } = useSimulationContext(); - const { petriNetDefinition } = useEditorContext(); - - const currentTokenCounts = placeMarkingsById[id] ?? {}; - - const nonZeroTokens = Object.entries(currentTokenCounts).filter( - ([_, count]) => count > 0, - ); - - const { parentNetNode } = data; - - return ( - - {parentNetNode && ( - - palette.gray[60], - position: "absolute", - top: 10, - left: nodeDimensions.place.width / 2 - 8, - fontSize: 16, - zIndex: 3, - }} - /> - - )} - - - {data.label} - - {nonZeroTokens.map(([tokenTypeId, count], index) => { - const tokenType = petriNetDefinition.tokenTypes.find( - (tt) => tt.id === tokenTypeId, - ); - - const position = getTokenPosition(index); - - return ( - palette.common.white, - fontSize: 12, - fontWeight: "bold", - padding: "0 4px", - zIndex: 3, - }} - > - {count} - - ); - })} - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx deleted file mode 100644 index ecd87e8f709..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/sidebar.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Box, Button, Stack } from "@mui/material"; -import type { DragEvent } from "react"; -import { useCallback } from "react"; - -import { useEditorContext } from "./editor-context"; -import { placeStyling, transitionStyling } from "./styling"; -import { useLayoutGraph } from "./use-layout-graph"; - -export const Sidebar = () => { - const onDragStart = useCallback( - (event: DragEvent, nodeType: "place" | "transition") => { - event.dataTransfer.setData("application/reactflow", nodeType); - - // eslint-disable-next-line no-param-reassign - event.dataTransfer.effectAllowed = "move"; - }, - [], - ); - - const { petriNetDefinition } = useEditorContext(); - - const layoutGraph = useLayoutGraph(); - - return ( - palette.gray[5], - p: 2, - borderRight: ({ palette }) => `1px solid ${palette.gray[30]}`, - }} - > - - onDragStart(event, "place")} - > - Place - - onDragStart(event, "transition")} - > - Transition - - - palette.gray[30], - height: "1px", - width: "100%", - my: 2, - }} - /> - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx deleted file mode 100644 index 4c5ebb50245..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-context.tsx +++ /dev/null @@ -1,341 +0,0 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "react"; - -import { useEditorContext } from "./editor-context"; -import type { ArcType, PlaceMarkingsById, TransitionNodeType } from "./types"; - -/** - * Check if a transition is enabled, i.e. that the tokens required by each incoming arc are available in the source place. - */ -const checkTransitionEnabled = ({ - transitionId, - arcs, - placeMarkingsById, -}: { - transitionId: string; - arcs: ArcType[]; - placeMarkingsById: PlaceMarkingsById; -}): boolean => { - const incomingArcs = arcs.filter((arc) => arc.target === transitionId); - - return incomingArcs.every((arc) => { - const tokenCounts = placeMarkingsById[arc.source]; - - if (!tokenCounts) { - throw new Error(`Could not find token counts for place ${arc.source}`); - } - - return Object.entries(arc.data?.tokenWeights ?? {}).every( - ([tokenTypeId, requiredWeight]) => { - const availableTokens = tokenCounts[tokenTypeId] ?? 0; - return availableTokens >= (requiredWeight ?? 0); - }, - ); - }); -}; - -type SimulationContextValue = { - currentStep: number; - fireNextStep: () => void; - isSimulating: boolean; - placeMarkingsById: PlaceMarkingsById; - simulationSpeed: number; - simulationLogs: Array<{ id: string; text: string }>; - setSimulationSpeed: (speed: number) => void; - setIsSimulating: (simulating: boolean) => void; - resetSimulation: () => void; -}; - -const SimulationContext = createContext( - undefined, -); - -interface SimulationProviderProps { - children: React.ReactNode; -} - -export const SimulationContextProvider = ({ - children, -}: SimulationProviderProps) => { - const [isSimulating, setIsSimulating] = useState(false); - const [simulationSpeed, setSimulationSpeed] = useState(1000); // ms delay between each step occuring when simulating - const [currentStep, setCurrentStep] = useState(0); - const [simulationLogs, setSimulationLogs] = useState< - Array<{ id: string; text: string }> - >([]); - const [placeMarkingsById, setPlaceMarkingsById] = useState( - {}, - ); - - const { petriNetDefinition } = useEditorContext(); - - const resetMarkings = useCallback(() => { - setPlaceMarkingsById( - petriNetDefinition.nodes.reduce((acc, node) => { - if (node.data.type === "place") { - acc[node.id] = node.data.initialTokenCounts ?? {}; - } - return acc; - }, {} as PlaceMarkingsById), - ); - }, [petriNetDefinition.nodes]); - - const resetSimulation = useCallback(() => { - setIsSimulating(false); - setSimulationLogs([]); - setCurrentStep(0); - resetMarkings(); - }, [resetMarkings]); - - useEffect(() => { - resetSimulation(); - }, [petriNetDefinition.nodes, resetSimulation]); - - const addLogEntry = useCallback( - (message: string) => { - const timestamp = Date.now(); - setSimulationLogs((prevLogs) => { - const newLog = { - id: `log-${timestamp}-${prevLogs.length}`, - text: `[Step ${currentStep}] ${message}`, - }; - return [...prevLogs, newLog]; - }); - }, - [currentStep], - ); - - const fireNextStep = useCallback(() => { - setCurrentStep((prevStep) => prevStep + 1); - - const enabledTransitions = petriNetDefinition.nodes - .filter((node) => node.type === "transition") - .filter((node) => - checkTransitionEnabled({ - transitionId: node.id, - arcs: petriNetDefinition.arcs, - placeMarkingsById, - }), - ); - - if (enabledTransitions.length === 0) { - setIsSimulating(false); - return; - } - - const updatedMarkings = JSON.parse( - JSON.stringify(placeMarkingsById), - ) as PlaceMarkingsById; - - /** - * @todo this logic is wrong, because a transition may be no longer enabled, - * if a transition fires before it and consumes a token it needs. - */ - for (const transition of enabledTransitions) { - const transitionNode = petriNetDefinition.nodes.find( - (node): node is TransitionNodeType => - node.id === transition.id && node.data.type === "transition", - ); - - if (!transitionNode) { - throw new Error(`Transition node ${transition.id} not found`); - } - - let outgoingArc: ArcType | undefined; - if (transitionNode.data.conditions?.length) { - /** - * If the transition has multiple outputs, we need to select one of them randomly based on the probability of each condition. - */ - const randomValue = Math.random() * 100; - let cumulativeProbability = 0; - let selectedCondition = null; - - for (const condition of transitionNode.data.conditions) { - cumulativeProbability += - typeof condition.probability === "number" - ? condition.probability - : 0; - - if (randomValue <= cumulativeProbability) { - selectedCondition = condition; - break; - } - } - - if (!selectedCondition) { - throw new Error("No condition was selected"); - } - - outgoingArc = petriNetDefinition.arcs.find( - (arc) => - arc.source === transition.id && - arc.id === selectedCondition.outputEdgeId, - ); - - addLogEntry( - `Transition "${transitionNode.data.label}" resulted in condition: ${selectedCondition.name} (${selectedCondition.probability}% probability)`, - ); - } else { - /** - * If the transition has only one output, we can just use that. - */ - outgoingArc = petriNetDefinition.arcs.find( - (arc) => arc.source === transition.id, - ); - } - - if (!outgoingArc) { - throw new Error( - `Outgoing edge for transition ${transition.id} not found`, - ); - } - - const incomingArcs = petriNetDefinition.arcs.filter( - (arc) => arc.target === transition.id, - ); - - if (incomingArcs.length === 0) { - throw new Error( - `Expected at least 1 incoming arc for transition ${transition.id}`, - ); - } - - /** - * Update the token counts of each source node based on the requirements of the incoming arc. - */ - for (const incomingArc of incomingArcs) { - for (const [tokenTypeId, weight] of Object.entries( - incomingArc.data?.tokenWeights ?? {}, - )) { - if (!weight) { - continue; - } - - const sourceMarkings = updatedMarkings[incomingArc.source]; - - if (!sourceMarkings) { - throw new Error( - `Could not find token counts for place ${incomingArc.source}`, - ); - } - - sourceMarkings[tokenTypeId] = - (sourceMarkings[tokenTypeId] ?? 0) - weight; - - /** - * This triggers the token animating along the input arc. - * To fully separate simulation logic from the UI, we should instead have the 'step' function be something the UI can call, - * and return the fired transitions from it. - */ - window.dispatchEvent( - new CustomEvent("animateTokenAlongArc", { - detail: { - arcId: incomingArc.id, - tokenTypeId, - }, - }), - ); - } - } - - for (const [tokenTypeId, weight] of Object.entries( - outgoingArc.data?.tokenWeights ?? {}, - )) { - if (!weight) { - continue; - } - - const targetMarkings = updatedMarkings[outgoingArc.target]; - - if (!targetMarkings) { - throw new Error( - `Could not find token counts for place ${outgoingArc.target}`, - ); - } - - targetMarkings[tokenTypeId] = - (targetMarkings[tokenTypeId] ?? 0) + weight; - - /** - * This triggers the token animating along the output arc. - * To fully separate simulation logic from the UI, we should instead have the 'step' function be something the UI can call, - * and return the fired transitions from it. - */ - setTimeout(() => { - window.dispatchEvent( - new CustomEvent("animateTokenAlongArc", { - detail: { - arcId: outgoingArc.id, - tokenTypeId, - }, - }), - ); - /** - * Delay the animation so that it starts after the token animating across the input arc has finished. - */ - }, simulationSpeed / 2); - } - } - - setPlaceMarkingsById(updatedMarkings); - }, [ - petriNetDefinition.arcs, - petriNetDefinition.nodes, - addLogEntry, - simulationSpeed, - placeMarkingsById, - ]); - - useEffect(() => { - if (!isSimulating) { - return undefined; - } - - const interval = setInterval(fireNextStep, simulationSpeed); - - return () => clearInterval(interval); - }, [isSimulating, simulationSpeed, fireNextStep]); - - const contextValue = useMemo( - () => ({ - currentStep, - fireNextStep, - isSimulating, - placeMarkingsById, - resetSimulation, - setIsSimulating, - setSimulationSpeed, - simulationLogs, - simulationSpeed, - }), - [ - currentStep, - fireNextStep, - isSimulating, - placeMarkingsById, - resetSimulation, - simulationLogs, - simulationSpeed, - ], - ); - - return ( - - {children} - - ); -}; - -export const useSimulationContext = (): SimulationContextValue => { - const context = useContext(SimulationContext); - if (!context) { - throw new Error("useSimulation must be used within a SimulationProvider"); - } - return context; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx deleted file mode 100644 index 788e56dae72..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/simulation-controls.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { - ArrowsRotateRegularIcon, - ForwardStepSolidIcon, - PlaySolidIcon, - Select, - StopSolidIcon, -} from "@hashintel/design-system"; -import { - Box, - Button, - FormControl, - MenuItem, - Stack, - type SvgIconProps, - Tooltip, - Typography, -} from "@mui/material"; -import type { FunctionComponent } from "react"; - -import { useSimulationContext } from "./simulation-context"; - -const SimulationControlButton = ({ - background, - disabled, - Icon, - onClick, - tooltip, -}: { - background: "blue" | "red"; - disabled: boolean; - Icon: FunctionComponent; - onClick: () => void; - tooltip: string; -}) => { - return ( - - - - - - ); -}; - -export const SimulationControls = () => { - const { - isSimulating, - setIsSimulating, - fireNextStep, - resetSimulation, - simulationSpeed, - setSimulationSpeed, - currentStep, - } = useSimulationContext(); - - return ( - - {/* Simulation control buttons */} - - {isSimulating ? ( - setIsSimulating(false)} - tooltip="Stop simulation" - /> - ) : ( - setIsSimulating(true)} - tooltip="Start simulation" - /> - )} - - - - - - {/* Simulation step display */} - - - Step: - - {currentStep} - - - {/* Speed control (the interval between steps) */} - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts deleted file mode 100644 index 771b175bef4..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/styling.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { customColors } from "@hashintel/design-system/theme"; -import type { Theme } from "@mui/material"; -import type { SystemStyleObject } from "@mui/system"; - -export const nodeDimensions = { - place: { width: 130, height: 130 }, - transition: { width: 160, height: 80 }, -}; - -export const placeStyling: (theme: Theme) => SystemStyleObject = ({ - palette, -}) => ({ - padding: 1.5, - borderRadius: "50%", - width: nodeDimensions.place.width, - height: nodeDimensions.place.height, - display: "flex", - justifyContent: "center", - alignItems: "center", - background: palette.gray[10], - border: `2px solid ${palette.gray[50]}`, - fontSize: 15, - boxSizing: "border-box", - position: "relative", - textAlign: "center", - lineHeight: 1.3, -}); - -export const transitionStyling: (theme: Theme) => SystemStyleObject = ({ - palette, -}) => ({ - padding: 1.5, - borderRadius: 2, - width: nodeDimensions.transition.width, - height: nodeDimensions.transition.height, - display: "flex", - flexDirection: "column", - justifyContent: "center", - alignItems: "center", - background: palette.gray[20], - border: `2px solid ${palette.gray[50]}`, - fontSize: 15, - boxSizing: "border-box", - position: "relative", -}); - -export const handleStyling = { - background: customColors.gray[60], - width: 10, - height: 10, - borderRadius: "50%", - zIndex: 3, -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx deleted file mode 100644 index 802029be7c8..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/title-and-net-select.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { faAngleRight } from "@fortawesome/free-solid-svg-icons"; -import { EditableField } from "@hashintel/block-design-system"; -import { FontAwesomeIcon } from "@hashintel/design-system"; -import { Stack, Typography } from "@mui/material"; - -import { useEditorContext } from "./editor-context"; -import { NetSelector } from "./net-selector"; - -export const TitleAndNetSelect = () => { - const { existingNets, loadPetriNet, parentNet, petriNetId, setTitle, title } = - useEditorContext(); - - return ( - ({ - background: palette.gray[5], - borderBottom: `1px solid ${palette.gray[20]}`, - py: 1, - px: 2, - })} - > - - {parentNet && ( - <> - { - loadPetriNet(parentNet.parentNetId); - }} - sx={({ palette, transitions }) => ({ - background: "none", - border: "none", - color: palette.gray[80], - cursor: "pointer", - transition: transitions.create(["color"], { - duration: 150, - }), - whiteSpace: "nowrap", - "&:hover": { - color: palette.common.black, - }, - })} - > - {parentNet.title} - - ({ - fontSize: 14, - color: palette.gray[50], - mx: 0, - })} - /> - - )} - setTitle(event.target.value)} - placeholder="Process" - /> - - - { - loadPetriNet(net.netId); - }} - options={existingNets.map((net) => ({ - netId: net.netId, - title: net.title, - }))} - value={petriNetId} - /> - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx deleted file mode 100644 index 77e544d11f2..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useState } from "react"; - -import { useEditorContext } from "./editor-context"; -import { TokenTypeEditor } from "./token-types/token-type-editor"; - -export { defaultTokenTypes } from "./token-types/default-token-types"; - -export const TokenTypes = () => { - const [tokenTypeEditorOpen, setTokenTypeEditorOpen] = useState(false); - - const { petriNetDefinition } = useEditorContext(); - - return ( - <> - setTokenTypeEditorOpen(false)} - /> - - {petriNetDefinition.tokenTypes.map((token) => ( - - - {token.name} - - ))} - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts deleted file mode 100644 index 9c35ce2b7a5..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/default-token-types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { TokenType } from "../types"; - -export const defaultTokenTypes: TokenType[] = [ - { id: "default", name: "Default", color: "#3498db" }, -]; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx deleted file mode 100644 index 61fda2ba932..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/token-types/token-type-editor.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import { TextField } from "@hashintel/design-system"; -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - Stack, - Typography, -} from "@mui/material"; -import { useEffect, useState } from "react"; - -import { useEditorContext } from "../editor-context"; -import type { TokenType } from "../types"; - -type TokenTypeEditorProps = { - open: boolean; - onClose: () => void; -}; - -export const TokenTypeEditor = ({ open, onClose }: TokenTypeEditorProps) => { - const { petriNetDefinition, mutatePetriNetDefinition } = useEditorContext(); - - const [localTokenTypes, setLocalTokenTypes] = useState( - petriNetDefinition.tokenTypes, - ); - - useEffect(() => { - setLocalTokenTypes(petriNetDefinition.tokenTypes); - }, [petriNetDefinition.tokenTypes]); - - const [newTokenName, setNewTokenName] = useState(""); - const [newTokenColor, setNewTokenColor] = useState("#3498db"); - - const handleAddToken = () => { - if (newTokenName.trim()) { - const newToken: TokenType = { - id: `token-${Date.now()}`, - name: newTokenName.trim(), - color: newTokenColor, - }; - - setLocalTokenTypes([...localTokenTypes, newToken]); - setNewTokenName(""); - setNewTokenColor("#3498db"); - } - }; - - const handleDeleteToken = (id: string) => { - if (localTokenTypes.length <= 1) { - return; - } - - setLocalTokenTypes(localTokenTypes.filter((token) => token.id !== id)); - }; - - const handleTokenNameChange = (id: string, name: string) => { - setLocalTokenTypes( - localTokenTypes.map((token) => - token.id === id ? { ...token, name } : token, - ), - ); - }; - - /** - * @todo use more granular mutations of the token types - * possibly just get rid of the local state and 'save' button and mutate on every change - */ - const handleSave = () => { - mutatePetriNetDefinition((existingNet) => { - // eslint-disable-next-line no-param-reassign - existingNet.tokenTypes = localTokenTypes; - }); - - onClose(); - }; - - return ( - - - - - - Token Types - - - {localTokenTypes.map((token) => ( - - - - { - setLocalTokenTypes( - localTokenTypes.map((tok) => - tok.id === token.id - ? { ...tok, color: event.currentTarget.value } - : tok, - ), - ); - }} - sx={{ - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - opacity: 0, - cursor: "pointer", - }} - /> - - - handleTokenNameChange(token.id, event.target.value) - } - size="small" - sx={{ flex: 1 }} - onClick={(event) => event.stopPropagation()} - /> - - - ))} - - - - - Add New - - - setNewTokenName(event.target.value)} - placeholder="Token name" - size="small" - /> - - - - setNewTokenColor(event.currentTarget.value) - } - sx={{ - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - opacity: 0, - cursor: "pointer", - }} - /> - - - - - - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx deleted file mode 100644 index 7bf17691e19..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-editor.tsx +++ /dev/null @@ -1,795 +0,0 @@ -import { - IconButton, - Select, - TextField, - XMarkRegularIcon, -} from "@hashintel/design-system"; -import { - Box, - Button, - Checkbox, - Dialog, - DialogActions, - DialogContent, - Divider, - FormControlLabel, - MenuItem, - Slider, - Stack, - Switch, - Tooltip, - Typography, -} from "@mui/material"; -import { useCallback, useMemo, useState } from "react"; - -import { useEditorContext } from "./editor-context"; -import { generateUuid } from "./generate-uuid"; -import { NetSelector } from "./net-selector"; -import type { - ArcType, - PlaceNodeType, - TransitionCondition, - TransitionNodeData, - TransitionNodeType, -} from "./types"; - -const normalizeProbabilities = ( - conditions: TransitionCondition[], -): TransitionCondition[] => { - if (conditions.length === 0) { - return []; - } - - if (conditions.length === 1) { - return [ - { - ...conditions[0]!, - probability: 100, - }, - ]; - } - - const totalProbability = conditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - if (totalProbability === 100) { - return conditions; - } - - const factor = 100 / totalProbability; - const normalizedConditions = conditions.map((condition) => ({ - ...condition, - probability: Math.round(condition.probability * factor), - })); - - // Fix any rounding errors by adjusting the first condition - const newTotal = normalizedConditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - if (newTotal !== 100 && normalizedConditions.length > 0) { - normalizedConditions[0] = { - ...normalizedConditions[0]!, - probability: normalizedConditions[0]!.probability + (100 - newTotal), - }; - } - - return normalizedConditions; -}; - -const distributeRemovedProbability = ( - conditions: TransitionCondition[], - removedProbability: number, -): TransitionCondition[] => { - if (conditions.length === 0) { - return []; - } - if (conditions.length === 1) { - return normalizeProbabilities(conditions); - } - - const totalRemainingProbability = conditions.reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - - const distributionFactor = - totalRemainingProbability > 0 - ? removedProbability / totalRemainingProbability - : 0; - - return normalizeProbabilities( - conditions.map((condition) => ({ - ...condition, - probability: Math.round(condition.probability * (1 + distributionFactor)), - })), - ); -}; - -const distributeEvenlyWithRemainder = (count: number): number[] => { - const targetProbability = Math.floor(100 / count); - const remainder = 100 - targetProbability * count; - - return Array(count) - .fill(targetProbability) - .map((prob: number, idx) => (idx === 0 ? prob + remainder : prob)); -}; - -type TransitionEditorProps = { - open: boolean; - onClose: () => void; - transitionId: string; - outgoingEdges: Array< - ArcType & { - targetLabel: string; - } - >; - onUpdateTransition: ( - transitionId: string, - data: Omit, - ) => void; -}; - -export const TransitionEditor = ({ - open, - onClose, - transitionId, - outgoingEdges, - onUpdateTransition, -}: TransitionEditorProps) => { - const { petriNetDefinition } = useEditorContext(); - - const { transitionNode, allInputPlaces, allOutputPlaces } = useMemo(() => { - const node = petriNetDefinition.nodes.find( - (option): option is TransitionNodeType => - option.data.type === "transition" && option.id === transitionId, - ); - - if (!node) { - throw new Error("Transition node not found"); - } - - const inputPlaces: PlaceNodeType[] = []; - const outputPlaces: PlaceNodeType[] = []; - - for (const arc of petriNetDefinition.arcs) { - if (arc.source === transitionId) { - const outputPlace = petriNetDefinition.nodes.find( - (option): option is PlaceNodeType => - option.type === "place" && option.id === arc.target, - ); - - if (!outputPlace) { - throw new Error("Output place not found"); - } - - outputPlaces.push(outputPlace); - } - - if (arc.target === transitionId) { - const inputPlace = petriNetDefinition.nodes.find( - (option): option is PlaceNodeType => - option.type === "place" && option.id === arc.source, - ); - - if (!inputPlace) { - throw new Error("Input place not found"); - } - - inputPlaces.push(inputPlace); - } - } - - return { - transitionNode: node, - allInputPlaces: inputPlaces, - allOutputPlaces: outputPlaces, - }; - }, [petriNetDefinition.arcs, petriNetDefinition.nodes, transitionId]); - - const [localData, setEditedData] = useState>( - { - label: transitionNode.data.label, - description: transitionNode.data.description ?? "", - conditions: transitionNode.data.conditions ?? [], - delay: transitionNode.data.delay, - childNet: transitionNode.data.childNet, - }, - ); - - const hasConditions = localData.conditions && localData.conditions.length > 0; - - const { existingNets } = useEditorContext(); - - const updateChildNet = useCallback( - ({ - childNetId, - childNetTitle, - inputPlaceIds, - outputPlaceIds, - }: { - childNetId: string; - childNetTitle: string; - inputPlaceIds: string[]; - outputPlaceIds: string[]; - }) => { - setEditedData((prev) => ({ - ...prev, - childNet: { - childNetId, - childNetTitle, - inputPlaceIds, - outputPlaceIds, - }, - })); - }, - [], - ); - - const handleHasConditionsChange = useCallback( - (event: React.ChangeEvent) => { - setEditedData((prev) => ({ - ...prev, - conditions: event.target.checked - ? [ - { - id: `condition-${Date.now()}`, - name: "Default", - probability: 100, - outputEdgeId: outgoingEdges[0]!.id, - }, - ] - : [], - })); - }, - [outgoingEdges], - ); - - const handleAddCondition = useCallback(() => { - setEditedData((prev) => { - const existingConditions = prev.conditions ?? []; - - if (existingConditions.length === 0) { - const newCondition: TransitionCondition = { - id: `condition-${Date.now()}`, - name: "Condition 1", - probability: 100, - outputEdgeId: outgoingEdges[0]!.id, - }; - - return { - ...prev, - conditions: [newCondition], - }; - } - - const newConditionCount = existingConditions.length + 1; - const probabilities = distributeEvenlyWithRemainder(newConditionCount); - - const adjustedConditions = existingConditions.map((condition, index) => ({ - ...condition, - probability: probabilities[index]!, - })); - - const newCondition: TransitionCondition = { - id: `condition-${generateUuid()}`, - name: `Condition ${newConditionCount}`, - probability: probabilities[newConditionCount - 1]!, - outputEdgeId: outgoingEdges[0]!.id, - }; - - return { - ...prev, - conditions: [...adjustedConditions, newCondition], - }; - }); - }, [outgoingEdges]); - - const handleConditionNameChange = useCallback( - (conditionId: string, name: string) => { - setEditedData((prev) => ({ - ...prev, - conditions: (prev.conditions ?? []).map((condition) => - condition.id === conditionId ? { ...condition, name } : condition, - ), - })); - }, - [], - ); - - const handleRemoveCondition = useCallback((conditionId: string) => { - setEditedData((prev) => { - const conditions = prev.conditions ?? []; - const conditionToRemove = conditions.find( - (condition) => condition.id === conditionId, - ); - - if (!conditionToRemove) { - return prev; - } - - const remainingConditions = conditions.filter( - (condition) => condition.id !== conditionId, - ); - return { - ...prev, - conditions: distributeRemovedProbability( - remainingConditions, - conditionToRemove.probability, - ), - }; - }); - }, []); - - const handleConditionProbabilityChange = useCallback( - (conditionId: string, newProbability: number) => { - setEditedData((prev) => { - const conditions = prev.conditions ?? []; - const updatedConditions = conditions.map((condition) => - condition.id === conditionId - ? { ...condition, probability: newProbability } - : condition, - ); - return { - ...prev, - conditions: normalizeProbabilities(updatedConditions), - }; - }); - }, - [], - ); - - const handleToggleEdgeForCondition = useCallback( - (conditionId: string, edgeId: string) => { - setEditedData((prev) => ({ - ...prev, - conditions: (prev.conditions ?? []).map((condition) => { - if (condition.id === conditionId) { - return { - ...condition, - outputEdgeId: edgeId, - }; - } - return condition; - }), - })); - }, - [], - ); - - const handleSave = useCallback(() => { - const dataToSave = { ...localData }; - - if (dataToSave.conditions && dataToSave.conditions.length > 0) { - dataToSave.conditions = normalizeProbabilities(dataToSave.conditions); - } - - onUpdateTransition(transitionId, dataToSave); - onClose(); - }, [transitionId, localData, onUpdateTransition, onClose]); - - const totalProbability = useMemo(() => { - return (localData.conditions ?? []).reduce( - (sum, condition) => sum + condition.probability, - 0, - ); - }, [localData.conditions]); - - const existingNetsAvailable = existingNets.length > 0; - - return ( - - - - - - Name - - - setEditedData((prev) => ({ - ...prev, - label: event.target.value, - })) - } - fullWidth - size="small" - /> - - - - - Description - - - setEditedData((prev) => ({ - ...prev, - description: event.target.value, - })) - } - fullWidth - size="small" - /> - - - {/* - - Delay (hours) - - - - setEditedData((prev) => ({ - ...prev, - delay: Number.isNaN(event.target.value) - ? undefined - : parseFloat(event.target.value), - })) - } - inputProps={{ min: 0, step: 0.5 }} - sx={{ width: 80 }} - /> - */} - - `1px solid ${palette.gray[20]}`, - borderRadius: 2, - p: 2, - display: existingNetsAvailable ? "block" : "none", - }} - > - {existingNetsAvailable && ( - - - Child net - - - - updateChildNet({ - inputPlaceIds: allInputPlaces.map((place) => place.id), - outputPlaceIds: allOutputPlaces.map((place) => place.id), - ...localData.childNet, - childNetId: value.netId, - childNetTitle: value.title, - }) - } - /> - - )} - - {localData.childNet && ( - <> - - palette.common.black, - }} - > - input places - - - {allInputPlaces.map((place) => { - const isInputPlaceChecked = - !!localData.childNet?.inputPlaceIds.includes(place.id); - const onlyInputPlaceChecked = - localData.childNet?.inputPlaceIds.length === 1 && - isInputPlaceChecked; - - return ( - - { - const isChecked = event.target.checked; - const currentInputPlaceIds = - localData.childNet?.inputPlaceIds ?? []; - const newInputPlaceIds = isChecked - ? [...currentInputPlaceIds, place.id] - : currentInputPlaceIds.filter( - (id) => id !== place.id, - ); - - updateChildNet({ - ...localData.childNet!, - inputPlaceIds: newInputPlaceIds, - outputPlaceIds: - localData.childNet?.outputPlaceIds ?? [], - childNetId: localData.childNet!.childNetId, - childNetTitle: - localData.childNet!.childNetTitle, - }); - }} - sx={{ mr: 0.8 }} - /> - } - label={place.data.label} - sx={{ m: 0 }} - /> - - ); - })} - - - - - palette.common.black, - }} - > - output places - - - {allOutputPlaces.map((place) => { - const isOutputPlaceChecked = - !!localData.childNet?.outputPlaceIds.includes(place.id); - const onlyOutputPlaceChecked = - localData.childNet?.outputPlaceIds.length === 1 && - isOutputPlaceChecked; - - return ( - - { - const isChecked = event.target.checked; - const currentOutputPlaceIds = - localData.childNet?.outputPlaceIds ?? []; - const newOutputPlaceIds = isChecked - ? [...currentOutputPlaceIds, place.id] - : currentOutputPlaceIds.filter( - (id) => id !== place.id, - ); - - updateChildNet({ - ...localData.childNet!, - inputPlaceIds: - localData.childNet?.inputPlaceIds ?? [], - outputPlaceIds: newOutputPlaceIds, - childNetId: localData.childNet!.childNetId, - childNetTitle: - localData.childNet!.childNetTitle, - }); - }} - sx={{ mr: 0.8 }} - /> - } - label={place.data.label} - sx={{ m: 0 }} - /> - - ); - })} - - - - )} - - - - - - - - - This transition has multiple possible outcomes - - - - {hasConditions && ( - - - - totalProbability === 100 - ? palette.green[70] - : palette.red[80], - }} - > - Total Probability: {totalProbability}% - - - - - - {(localData.conditions ?? []).map((condition) => ( - - `1px solid ${palette.gray[20]}`, - borderRadius: 2, - }} - > - - - - - Condition Name - - - handleConditionNameChange( - condition.id, - event.target.value, - ) - } - fullWidth - size="small" - /> - - {(localData.conditions?.length ?? 0) > 1 && ( - - handleRemoveCondition(condition.id) - } - > - - - )} - - - - - Probability: {condition.probability}% - - - handleConditionProbabilityChange( - condition.id, - typeof value === "number" ? value : value[0]!, - ) - } - valueLabelDisplay="auto" - step={1} - min={1} - max={100} - /> - - - - - - Output to - - - {outgoingEdges.length === 0 ? ( - - No output paths available. Add arcs from this - transition first. - - ) : ( - - - - )} - - - - ))} - - - )} - - - - - - - - - ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx b/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx deleted file mode 100644 index 2278c1631b7..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/transition-node.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { IconButton, IconDiagramRegular } from "@hashintel/design-system"; -import { Box, Tooltip, Typography } from "@mui/material"; -import { Handle, type NodeProps, Position } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { handleStyling, transitionStyling } from "./styling"; -import type { TransitionNodeData } from "./types"; - -export const TransitionNode = ({ - data, - isConnectable, -}: NodeProps) => { - const { label, description, childNet } = data; - - const { loadPetriNet } = useEditorContext(); - - return ( -
- - - {childNet && ( - - { - event.stopPropagation(); - - loadPetriNet(childNet.childNetId); - }} - sx={{ - position: "absolute", - top: 0, - left: 0, - "&:hover": { - background: "transparent", - "& svg": { - fill: ({ palette }) => palette.blue[70], - }, - }, - }} - > - - - - )} - - {label} - - {description && ( - - {description} - - )} - - -
- ); -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts deleted file mode 100644 index 7b85fa4c24f..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/types.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Edge, Node } from "reactflow"; - -export type ArcData = { - tokenWeights: { - [tokenTypeId: string]: number | undefined; - }; -}; - -export type ArcType = Omit, "style">; - -export type PlaceNodeData = { - label: string; - initialTokenCounts?: TokenCounts; - /** - * If the net is a child net, it represents the detail of a transition from the parent. - * It must contain at least one each of input and output places to that parent transition. - * This field indicates if this place corresponds to an input or output place to the transition in the parent. - */ - parentNetNode?: { - id: string; - type: "input" | "output"; - }; - type: "place"; -}; - -export type PlaceMarkingsById = Record; - -export type PlaceNodeType = Omit< - Node, - "selected" | "dragging" ->; - -export type TransitionCondition = { - id: string; - name: string; - probability: number; - outputEdgeId: string; -}; - -export type TransitionNodeData = { - conditions?: TransitionCondition[]; - label: string; - delay?: number; - description?: string; - childNet?: { - childNetId: string; - childNetTitle: string; - /** - * The IDs of the input places for this transition which are represented in the childNet (which should appear as starting places there). - * There must be at least one, but not all input places to this transition (in the parent) must appear in the childNet. - */ - inputPlaceIds: string[]; - /** - * The IDs of the output places for this transition which are represented in the childNet (which should appear as ending places there). - * There must be at least one, but not all output places to this transition (in the parent) must appear in the childNet. - */ - outputPlaceIds: string[]; - }; - /** - * Although a reactflow {@link Node} has a 'type' field, the library types don't discriminate on this field in all methods, - * so we add our own discriminating field here to make it easier to narrow between Transition and Place nodes. - */ - type: "transition"; -}; - -export type TransitionNodeType = Omit< - Node, - "selected" | "dragging" ->; - -export type PetriNetDefinitionObject = { - arcs: ArcType[]; - nodes: NodeType[]; - tokenTypes: TokenType[]; -}; - -export type NodeData = PlaceNodeData | TransitionNodeData; - -export type NodeType = Node; - -export type TokenCounts = { - [tokenTypeId: string]: number; -}; - -export type TokenType = { - id: string; - name: string; - color: string; -}; - -export type MinimalNetMetadata = { - netId: string; - title: string; -}; - -export type ParentNet = { - parentNetId: string; - title: string; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts deleted file mode 100644 index 9a9a071c4f6..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-convert-to-pnml.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { - PetriNetDefinitionObject, - PlaceNodeData, - TransitionNodeData, -} from "./types"; - -const escapeXml = (str: string) => - str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - -/** - * Convert the current process to an ISO-15909-2-conformant HLPN PNML document. - * - * @todo the proper format is to be agreed, this current version is wrong. - */ -export const useConvertToPnml = ({ - petriNet, - title, -}: { - petriNet: PetriNetDefinitionObject; - title: string; -}) => { - const { nodes, arcs, tokenTypes } = petriNet; - - const convertToPnml = (): string => { - /* ---------- Header & namespaces---------- */ - let pnml = ` - - - - ${escapeXml(title)}`; - - /* ---------- HLPN colour-set declarations ---------- */ - pnml += ` - `; - for (const tok of tokenTypes) { - pnml += ` - - - ${escapeXml(tok.name)} - - - - - ${escapeXml(tok.color)} - - `; - } - pnml += ` - `; - - pnml += ` - `; - - /* ---------- Places & initial markings ---------- */ - for (const node of nodes) { - if (node.type !== "place") { - continue; - } - const placeData = node.data as PlaceNodeData; - - pnml += ` - - ${escapeXml(placeData.label)} - - - `; - - for (const [tokId, count] of Object.entries( - placeData.initialTokenCounts ?? {}, - )) { - if (count > 0) { - pnml += ` - - ${escapeXml(tokId)} - `; - } - } - - pnml += ` - - - `; - } - - /* ---------- Transitions ---------- */ - for (const node of nodes) { - if (node.type !== "transition") { - continue; - } - const transitionData = node.data as TransitionNodeData; - - pnml += ` - - ${escapeXml(transitionData.label)} - `; - - if ( - typeof transitionData.delay === "number" || - transitionData.conditions?.length || - transitionData.description - ) { - pnml += ` - `; - - if (transitionData.description) { - pnml += ` - ${escapeXml(transitionData.description)}`; - } - - if (typeof transitionData.delay === "number") { - pnml += ` - - ${transitionData.delay} - `; - } - - if (transitionData.conditions?.length) { - pnml += ` - `; - for (const condition of transitionData.conditions) { - pnml += ` - - ${escapeXml(condition.name)} - `; - } - pnml += ` - `; - } - - pnml += ` - `; - } - - pnml += ` - `; - } - - /* ---------- Arcs & HLPN inscriptions ---------- */ - for (const arc of arcs) { - pnml += ` - - - `; - - for (const [tokId, count] of Object.entries( - arc.data?.tokenWeights ?? {}, - )) { - if (count) { - pnml += ` - - ${escapeXml(tokId)} - `; - } - } - - pnml += ` - - - `; - } - - pnml += ` - - -`; - - return pnml; - }; - - return convertToPnml; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts deleted file mode 100644 index 2010072c486..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-layout-graph.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { ElkNode } from "elkjs"; -import ELK from "elkjs"; -import { useCallback } from "react"; -import { useReactFlow } from "reactflow"; - -import { useEditorContext } from "./editor-context"; -import { nodeDimensions } from "./styling"; -import type { ArcType, NodeType } from "./types"; - -/** - * @see https://eclipse.dev/elk/documentation/tooldevelopers - * @see https://rtsys.informatik.uni-kiel.de/elklive/json.html for JSON playground - */ -const elk = new ELK(); - -const graphPadding = 30; - -/** - * @see https://eclipse.dev/elk/reference.html - */ -const elkLayoutOptions: ElkNode["layoutOptions"] = { - "elk.algorithm": "layered", - "org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers": "100", - "elk.direction": "RIGHT", - "elk.padding": `[left=${graphPadding},top=${ - graphPadding - },right=${graphPadding},bottom=${graphPadding}]`, -}; - -export const useLayoutGraph = () => { - const { fitView } = useReactFlow(); - - const { mutatePetriNetDefinition } = useEditorContext(); - - const layoutGraph = useCallback( - ({ - nodes, - arcs, - animationDuration, - }: { - nodes: NodeType[]; - arcs: ArcType[]; - animationDuration: number; - }) => { - if (nodes.length === 0) { - return; - } - - const graph: ElkNode = { - id: "root", - children: JSON.parse( - JSON.stringify( - nodes.map((node) => ({ - ...node, - /** - * If we are loading a graph in full (e.g. from an example or PNML file), we need to set the visible width and height, - * so that ELK knows how much space to reserve for the node. - * - * @todo encode width/height as part of the graph data - */ - width: nodeDimensions[node.data.type].width, - height: nodeDimensions[node.data.type].height, - })), - ), - ) as ElkNode["children"], - edges: JSON.parse(JSON.stringify(arcs)) as ElkNode["edges"], - layoutOptions: elkLayoutOptions, - }; - - void elk.layout(graph).then((updatedElements) => { - mutatePetriNetDefinition((petriNet) => { - const nodesById = petriNet.nodes.reduce( - (acc, node) => { - acc[node.id] = node; - return acc; - }, - {} as Record, - ); - - /** - * ELK inserts the calculated position as a root 'x' and 'y'. - */ - for (const { x, y, id } of updatedElements.children ?? []) { - const node = nodesById[id]; - - if (!node) { - continue; - } - - if (x && node.position.x !== x) { - node.position.x = x; - } - - if (y && node.position.y !== y) { - node.position.y = y; - } - } - }); - - setTimeout( - () => - window.requestAnimationFrame(() => - fitView({ - duration: animationDuration, - padding: 0.03, - maxZoom: 1, - }), - ), - 300, - ); - }); - }, - [mutatePetriNetDefinition, fitView], - ); - - return layoutGraph; -}; diff --git a/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts b/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts deleted file mode 100644 index af20288e1a1..00000000000 --- a/libs/@hashintel/petrinaut-old/src/petrinaut/use-load-from-pnml.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { useCallback } from "react"; -import type { Edge, Node } from "reactflow"; - -import type { - ArcData, - ArcType, - NodeType, - PetriNetDefinitionObject, - PlaceNodeData, - TokenCounts, - TokenType, - TransitionCondition, - TransitionNodeData, -} from "./types"; - -const elementToText = (el: Element | null): string => - el?.textContent.trim() ?? ""; - -const getElementsBySelector = (selector: string, parentNode: ParentNode) => - Array.from(parentNode.querySelectorAll(selector)); - -const readMultiset = (multiset: Element | null): TokenCounts => { - const counts: TokenCounts = {}; - - if (!multiset) { - return counts; - } - - for (const el of getElementsBySelector("element", multiset)) { - const tokId = elementToText(el.querySelector("constant")); - const mult = parseInt(el.getAttribute("multiplicity") ?? "1", 10); - counts[tokId] = (counts[tokId] ?? 0) + mult; - } - return counts; -}; - -/** - * Parse a PNML document that follows the HASH “HLPN + toolspecific” dialect back into a {@link PetriNetDefinitionObject}. - * - * @todo this dialect is wrong and needs updating once we have agreed on the format. - */ -const parsePnml = ( - xml: string, -): { - nodes: NodeType[]; - arcs: ArcType[]; - title: string; - tokenTypes: TokenType[]; -} => { - const document = new DOMParser().parseFromString(xml, "application/xml"); - - const titleElement = document.querySelector("pnml > net > name > text"); - const title = elementToText(titleElement); - - const tokenTypes: TokenType[] = getElementsBySelector("colset", document).map( - (colset) => { - const id = colset.getAttribute("id") ?? ""; - const name = elementToText(colset.querySelector("atom")); - const color = elementToText( - colset.querySelector('toolspecific[tool="HASH"] > color'), - ); - return { id, name, color }; - }, - ); - - const placeNodes: Node[] = getElementsBySelector( - "place", - document, - ).map((placeNode) => { - const id = placeNode.getAttribute("id")!; - - const posEl = placeNode.querySelector("graphics > position"); - const position = { - x: parseFloat(posEl?.getAttribute("x") ?? "0"), - y: parseFloat(posEl?.getAttribute("y") ?? "0"), - }; - - const label = elementToText(placeNode.querySelector("name > text")); - - const tokenCounts = readMultiset( - placeNode.querySelector("initialMarking > multiset"), - ); - - const data: PlaceNodeData = { - label, - initialTokenCounts: tokenCounts, - type: "place", - }; - - return { id, type: "place", position, data }; - }); - - const transitionNodes: Node[] = getElementsBySelector( - "transition", - document, - ).map((transitionNode) => { - const id = transitionNode.getAttribute("id")!; - - const posEl = transitionNode.querySelector("graphics > position"); - const position = { - x: parseFloat(posEl?.getAttribute("x") ?? "0"), - y: parseFloat(posEl?.getAttribute("y") ?? "0"), - }; - - const label = elementToText(transitionNode.querySelector("name > text")); - - const toolSpecificContainer = transitionNode.querySelector( - 'toolspecific[tool="HASH"]', - ); - - const description = elementToText( - toolSpecificContainer?.querySelector("description") ?? null, - ); - - const delay = toolSpecificContainer - ? parseFloat( - elementToText(toolSpecificContainer.querySelector("timing > delay")), - ) - : undefined; - - const conditions: TransitionCondition[] = toolSpecificContainer - ? getElementsBySelector("routing > branch", toolSpecificContainer).map( - (b) => ({ - id: b.getAttribute("id") ?? "", - name: elementToText(b.querySelector("name > text")), - probability: parseFloat(b.getAttribute("probability") ?? "0"), - outputEdgeId: b.getAttribute("outputArc") ?? "", - }), - ) - : []; - - const data: TransitionNodeData = { - label, - delay: Number.isNaN(delay) ? undefined : delay, - description, - conditions: conditions.length ? conditions : undefined, - type: "transition", - }; - - return { id, type: "transition", position, data }; - }); - - const arcs: Edge[] = getElementsBySelector("arc", document).map( - (a): ArcType => { - const id = a.getAttribute("id") ?? ""; - const source = a.getAttribute("source") ?? ""; - const target = a.getAttribute("target") ?? ""; - - const tokenWeights = readMultiset( - a.querySelector("inscription > multiset"), - ); - - return { - id, - source, - target, - data: { tokenWeights }, - }; - }, - ); - - return { - nodes: [...placeNodes, ...transitionNodes], - arcs, - tokenTypes, - title, - }; -}; - -export const useLoadFromPnml = ({ - createNewNet, -}: { - createNewNet: (params: { - petriNetDefinition: PetriNetDefinitionObject; - title: string; - }) => void; -}) => { - const load = useCallback( - (xml: string) => { - const { nodes, arcs, tokenTypes, title } = parsePnml(xml); - - createNewNet({ - petriNetDefinition: { - arcs, - nodes, - tokenTypes, - }, - title, - }); - }, - [createNewNet], - ); - - return load; -}; diff --git a/libs/@hashintel/petrinaut-old/theme-override.d.ts b/libs/@hashintel/petrinaut-old/theme-override.d.ts deleted file mode 100644 index c9349a94364..00000000000 --- a/libs/@hashintel/petrinaut-old/theme-override.d.ts +++ /dev/null @@ -1 +0,0 @@ -import "@hashintel/design-system/theme-override"; diff --git a/libs/@hashintel/petrinaut-old/tsconfig.json b/libs/@hashintel/petrinaut-old/tsconfig.json deleted file mode 100644 index a9a3e52ffdb..00000000000 --- a/libs/@hashintel/petrinaut-old/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@local/tsconfig/legacy-base-tsconfig-to-refactor.json", - "compilerOptions": { - "jsx": "preserve", - "lib": ["dom", "dom.iterable", "ESNext"], - "noEmit": true - }, - "include": ["theme-override.d.ts", "./src/"], - "exclude": ["./src/petrinaut-sdcpn"] -} diff --git a/yarn.lock b/yarn.lock index 60611e72d41..05985c33129 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7536,15 +7536,6 @@ __metadata: languageName: node linkType: hard -"@handsontable/react@npm:16.1.1": - version: 16.1.1 - resolution: "@handsontable/react@npm:16.1.1" - peerDependencies: - handsontable: ">=16.0.0" - checksum: 10c0/e09d98485896dbecf5af42ae02d63ac8a0540e06b3ba5aab56175f7eb18b938c14663aa87736da2f0d6c7f30fe3a07b57a8f59bc2928d7360d74244ab3c3dd82 - languageName: node - linkType: hard - "@hapi/address@npm:^5.1.1": version: 5.1.1 resolution: "@hapi/address@npm:5.1.1" @@ -7770,39 +7761,6 @@ __metadata: languageName: unknown linkType: soft -"@hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old": - version: 0.0.0-use.local - resolution: "@hashintel/petrinaut-old@workspace:libs/@hashintel/petrinaut-old" - dependencies: - "@emotion/react": "npm:11.14.0" - "@fortawesome/free-solid-svg-icons": "npm:6.7.2" - "@handsontable/react": "npm:16.1.1" - "@hashintel/block-design-system": "workspace:*" - "@hashintel/design-system": "workspace:*" - "@local/eslint": "workspace:*" - "@mantine/hooks": "npm:8.3.5" - "@mui/material": "npm:5.18.0" - "@mui/system": "npm:5.18.0" - "@types/react": "npm:19.2.7" - "@types/react-dom": "npm:19.2.3" - "@vitejs/plugin-react": "npm:5.0.4" - elkjs: "npm:0.11.0" - eslint: "npm:9.39.2" - immer: "npm:10.1.3" - react: "npm:19.2.3" - react-dom: "npm:19.2.3" - reactflow: "npm:11.11.4" - typescript: "npm:5.9.3" - uuid: "npm:13.0.0" - vite: "npm:7.1.11" - web-worker: "npm:1.4.1" - zustand: "npm:5.0.8" - peerDependencies: - react: ^19.0.0 - react-dom: ^19.0.0 - languageName: unknown - linkType: soft - "@hashintel/petrinaut@workspace:*, @hashintel/petrinaut@workspace:libs/@hashintel/petrinaut": version: 0.0.0-use.local resolution: "@hashintel/petrinaut@workspace:libs/@hashintel/petrinaut" @@ -46160,27 +46118,6 @@ __metadata: languageName: node linkType: hard -"zustand@npm:5.0.8": - version: 5.0.8 - resolution: "zustand@npm:5.0.8" - peerDependencies: - "@types/react": ">=18.0.0" - immer: ">=9.0.6" - react: ">=18.0.0" - use-sync-external-store: ">=1.2.0" - peerDependenciesMeta: - "@types/react": - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - checksum: 10c0/e865a6f7f1c0e03571701db5904151aaa7acefd6f85541a117085e129bf16e8f60b11f8cc82f277472de5c7ad5dcb201e1b0a89035ea0b40c9d9aab3cd24c8ad - languageName: node - linkType: hard - "zustand@npm:^4.4.1": version: 4.5.5 resolution: "zustand@npm:4.5.5" From 0fd0a23e6983df55bd96488341dc18d03ea54585 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sun, 8 Feb 2026 04:23:49 +0100 Subject: [PATCH 03/18] Fix Petrinaut panel positioning and worker URL in lib build Switch panels from `position: fixed` to `position: absolute` so they stay contained within petrinaut-root when embedded in Hash. Add `position: relative` to `.petrinaut-root` as the positioning anchor. Enable `base: "./"` and add a `generateBundle` plugin to simplify Vite's worker URL output from `"" + new URL(...).href` to a clean `new URL("assets/...", import.meta.url)`. Co-Authored-By: Claude Opus 4.6 --- libs/@hashintel/petrinaut/src/index.css | 1 + .../Editor/components/BottomBar/bottom-bar.tsx | 2 +- .../views/Editor/panels/BottomPanel/panel.tsx | 2 +- .../views/Editor/panels/LeftSideBar/panel.tsx | 2 +- .../differential-equation-properties.tsx | 2 +- .../Editor/panels/PropertiesPanel/panel.tsx | 2 +- libs/@hashintel/petrinaut/vite.config.ts | 18 +++++++++++++++++- 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/libs/@hashintel/petrinaut/src/index.css b/libs/@hashintel/petrinaut/src/index.css index 0cd81926fa4..db7789981b3 100644 --- a/libs/@hashintel/petrinaut/src/index.css +++ b/libs/@hashintel/petrinaut/src/index.css @@ -1,5 +1,6 @@ @layer reset, base, tokens, recipes, utilities; .petrinaut-root { + position: relative; background-color: #fafafa; } diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx index f15b35acd8f..0fca9f54bce 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/bottom-bar.tsx @@ -40,7 +40,7 @@ const dividerStyle = css({ }); const bottomBarPositionStyle = css({ - position: "fixed", + position: "absolute", left: "[50%]", transform: "translateX(-50%)", zIndex: 1000, diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx index 92c62ccb7aa..a87b59dfc11 100644 --- a/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx +++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/BottomPanel/panel.tsx @@ -141,7 +141,7 @@ export const BottomPanel: React.FC = () => { { root: isLibMode ? undefined : "demo-site", // Use relative paths for library assets (fixes worker URL in webpack consumers) - // base: isLibMode ? "./" : undefined, + base: isLibMode ? "./" : undefined, resolve: { // Prefer browser exports from packages @@ -69,6 +69,22 @@ export default defineConfig(({ mode }) => { }, }), + // Vite lib mode emits worker URLs as `"" + new URL("assets/...", import.meta.url).href` + // wrapped in an outer `new URL(...)`. Simplify to `new URL("assets/...", import.meta.url)`. + isLibMode && { + name: "simplify-worker-url", + generateBundle(_, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type === "chunk") { + chunk.code = chunk.code.replace( + /new URL\(\s*\/\* @vite-ignore \*\/\s*"" \+ new URL\(("assets\/[^"]+"), import\.meta\.url\)\.href,\s*import\.meta\.url\s*\)/g, + "new URL($1, import.meta.url)", + ); + } + } + }, + }, + isLibMode && dts({ rollupTypes: true, From add7f60b5c46eaf74b673833f5ad4c8b21b7c0d2 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sun, 8 Feb 2026 15:09:42 +0100 Subject: [PATCH 04/18] Fix Petrinaut CSS scoping for HASH embedding Override light/dark conditions so color tokens land on .petrinaut-root instead of :where(:root,...). Enable CSS @layer polyfill so utilities are not overridden by HASH's unlayered global reset (* { padding: 0 }). Co-Authored-By: Claude Opus 4.6 --- libs/@hashintel/petrinaut/panda.config.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libs/@hashintel/petrinaut/panda.config.ts b/libs/@hashintel/petrinaut/panda.config.ts index 7fb7eb27fff..31258ac9396 100644 --- a/libs/@hashintel/petrinaut/panda.config.ts +++ b/libs/@hashintel/petrinaut/panda.config.ts @@ -10,6 +10,15 @@ export default defineConfig({ // Scope CSS variables to petrinaut root instead of :root cssVarRoot: ".petrinaut-root", + // Override light/dark conditions from ds-theme preset so that + // conditional tokens (colors) are scoped to .petrinaut-root instead of :root. + conditions: { + extend: { + light: ".petrinaut-root &", + dark: ".dark .petrinaut-root &, [data-theme='dark'] .petrinaut-root &", + }, + }, + // Where to look for css declarations include: ["./src/**/*.{js,jsx,ts,tsx}"], @@ -32,6 +41,10 @@ export default defineConfig({ }, }, + // Polyfill CSS @layer for embedding in HASH, where unlayered global + // resets (* { padding: 0 }) would otherwise override layered utilities. + polyfill: true, + importMap: "@hashintel/ds-helpers", presets: ["@hashintel/ds-theme"], }); From 856e0b3868c5fce5da05c1e76c6bead41d5968cb Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sun, 8 Feb 2026 20:22:16 +0100 Subject: [PATCH 05/18] Fix tooltip text inheriting bold/uppercase from parent elements Add CSS resets (fontWeight, textAlign, textTransform, letterSpacing) to tooltip content so it doesn't inherit styles from ancestor elements like section titles and field labels. Co-Authored-By: Claude Opus 4.6 --- .../petrinaut/src/components/tooltip.tsx | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/libs/@hashintel/petrinaut/src/components/tooltip.tsx b/libs/@hashintel/petrinaut/src/components/tooltip.tsx index 67bec7e6fe8..325679a3ab8 100644 --- a/libs/@hashintel/petrinaut/src/components/tooltip.tsx +++ b/libs/@hashintel/petrinaut/src/components/tooltip.tsx @@ -1,9 +1,7 @@ import { ark } from "@ark-ui/react/factory"; import { Tooltip as ArkTooltip } from "@ark-ui/react/tooltip"; import { css, cva, cx } from "@hashintel/ds-helpers/css"; -import type { SvgIconProps } from "@mui/material"; -import { SvgIcon, Tooltip as MuiTooltip } from "@mui/material"; -import type { FunctionComponent, ReactNode } from "react"; +import type { ReactNode } from "react"; const tooltipContentStyle = css({ backgroundColor: "neutral.s120", @@ -13,6 +11,12 @@ const tooltipContentStyle = css({ zIndex: "[10000]", boxShadow: "[0 2px 8px rgba(0, 0, 0, 0.15)]", padding: "[6px 10px]", + maxWidth: "[min(300px, var(--available-width))]", + wordWrap: "break-word", + fontWeight: "normal", + textAlign: "left", + textTransform: "none", + letterSpacing: "normal", }); const triggerWrapperStyle = cva({ @@ -71,7 +75,12 @@ export const Tooltip: React.FC = ({ @@ -87,40 +96,33 @@ export const Tooltip: React.FC = ({ ); }; -const CircleInfoIcon: FunctionComponent = (props) => { +const circleInfoIconStyle = css({ + display: "inline-block", + width: "[11px]", + height: "[11px]", + marginLeft: "[6.4px]", + marginBottom: "[1.6px]", + color: "[rgb(160, 160, 160)]", + verticalAlign: "middle", + fill: "[currentColor]", +}); + +const CircleInfoIcon = () => { return ( - + ); }; export const InfoIconTooltip = ({ tooltip }: { tooltip: string }) => { return ( - - - + + + ); }; From ba5ed3cd988f1caf4777608fadefd552321f4e44 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Sun, 8 Feb 2026 20:22:50 +0100 Subject: [PATCH 06/18] Remove @mui/material dependency from Petrinaut The last MUI usage (tooltip) was replaced with Ark UI and native SVG, so the dependency and its ESLint import restriction are no longer needed. Co-Authored-By: Claude Opus 4.6 --- libs/@hashintel/petrinaut/eslint.config.js | 4 ---- libs/@hashintel/petrinaut/package.json | 1 - yarn.lock | 1 - 3 files changed, 6 deletions(-) diff --git a/libs/@hashintel/petrinaut/eslint.config.js b/libs/@hashintel/petrinaut/eslint.config.js index 2051a37a9de..57cf07fa2b9 100644 --- a/libs/@hashintel/petrinaut/eslint.config.js +++ b/libs/@hashintel/petrinaut/eslint.config.js @@ -43,10 +43,6 @@ export default [ "error", { patterns: [ - { - group: ["@mui/material/*"], - message: "Please import from @mui/material instead", - }, { group: ["@local/*"], message: diff --git a/libs/@hashintel/petrinaut/package.json b/libs/@hashintel/petrinaut/package.json index 3c3ffeb7ea5..8b7a438041c 100644 --- a/libs/@hashintel/petrinaut/package.json +++ b/libs/@hashintel/petrinaut/package.json @@ -48,7 +48,6 @@ "@hashintel/refractive": "workspace:^", "@mantine/hooks": "8.3.5", "@monaco-editor/react": "4.8.0-rc.3", - "@mui/material": "5.18.0", "d3-array": "3.2.4", "d3-scale": "4.0.2", "elkjs": "0.11.0", diff --git a/yarn.lock b/yarn.lock index 05985c33129..6b84403daf2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7778,7 +7778,6 @@ __metadata: "@local/eslint": "workspace:*" "@mantine/hooks": "npm:8.3.5" "@monaco-editor/react": "npm:4.8.0-rc.3" - "@mui/material": "npm:5.18.0" "@pandacss/dev": "npm:1.4.3" "@sentry/react": "npm:10.22.0" "@testing-library/dom": "npm:10.4.1" From 83a800e8cd8285213918d3688373857ff7c46c7c Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Mon, 9 Feb 2026 04:27:18 +0100 Subject: [PATCH 07/18] Fix WebWorker compatibility with webpack consumers @babel/standalone bundles the `debug` package which accesses `window` in its `useColors()` without a typeof guard. This causes a "window is not defined" ReferenceError when the simulation worker initializes in a Web Worker context. Fix by prepending `self.window = self` to worker bundle assets in the Vite lib build plugin, aliasing `window` to the Worker global scope. Also add temporary debug logging to simulation initialization flow to help diagnose an "Initialization cancelled" error when Petrinaut is embedded in HASH frontend. Co-Authored-By: Claude Opus 4.6 --- .../petrinaut/src/playback/provider.tsx | 10 ++++++++ .../petrinaut/src/simulation/provider.tsx | 8 +++++++ .../worker/use-simulation-worker.ts | 13 ++++++++++ libs/@hashintel/petrinaut/vite.config.ts | 24 +++++++++++++++---- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/libs/@hashintel/petrinaut/src/playback/provider.tsx b/libs/@hashintel/petrinaut/src/playback/provider.tsx index c66306e6666..e249729b252 100644 --- a/libs/@hashintel/petrinaut/src/playback/provider.tsx +++ b/libs/@hashintel/petrinaut/src/playback/provider.tsx @@ -414,6 +414,10 @@ export const PlaybackProvider: React.FC = ({ const play: PlaybackContextValue["play"] = async () => { const simState = simulationStateRef.current; + console.log( + "[Petrinaut:Debug] PlaybackProvider.play() called, simState:", + simState, + ); const state = stateValuesRef.current; const { maxFramesAhead, batchSize } = getPlayModeBackpressure( state.playMode, @@ -421,6 +425,9 @@ export const PlaybackProvider: React.FC = ({ // Initialize simulation if not run yet if (simState === "NotRun") { + console.log( + "[Petrinaut:Debug] PlaybackProvider.play() -> calling initialize (simState was NotRun)", + ); await initialize({ seed: Date.now(), dt: dtRef.current, @@ -429,6 +436,9 @@ export const PlaybackProvider: React.FC = ({ }); // Initialization complete - start simulation // The effect will set playbackState to "Playing" when simulation starts running + console.log( + "[Petrinaut:Debug] PlaybackProvider.play() -> initialization complete, calling runSimulation()", + ); runSimulation(); return; } diff --git a/libs/@hashintel/petrinaut/src/simulation/provider.tsx b/libs/@hashintel/petrinaut/src/simulation/provider.tsx index b8cb07e7050..0bb0987142e 100644 --- a/libs/@hashintel/petrinaut/src/simulation/provider.tsx +++ b/libs/@hashintel/petrinaut/src/simulation/provider.tsx @@ -101,6 +101,11 @@ export const SimulationProvider: React.FC = ({ // Reinitialize when petriNetId changes useEffect(() => { + console.log( + "[Petrinaut:Debug] SimulationProvider: petriNetId changed to", + petriNetId, + "- calling reset()", + ); workerActions.reset(); setStateValues(INITIAL_STATE_VALUES); }, [petriNetId, workerActions]); @@ -155,6 +160,9 @@ export const SimulationProvider: React.FC = ({ // Delegate to worker (maxTime is immutable once set at initialization) // Returns a promise that resolves when initialization is complete + console.log( + "[Petrinaut:Debug] SimulationProvider.initialize() called, delegating to worker", + ); return workerActions.initialize({ sdcpn, initialMarking: currentState.initialMarking, diff --git a/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts b/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts index 7bb4d86b847..b2fffbb75f2 100644 --- a/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts +++ b/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts @@ -137,6 +137,9 @@ export function useSimulationWorker(): { switch (message.type) { case "ready": + console.log( + "[Petrinaut:Debug] Worker sent 'ready' message, resolving pending init", + ); setState((prev) => ({ ...prev, status: prev.status === "initializing" ? "ready" : prev.status, @@ -177,6 +180,10 @@ export function useSimulationWorker(): { break; case "error": + console.error( + "[Petrinaut:Debug] Worker sent 'error' message:", + message.message, + ); setState((prev) => ({ ...prev, status: "error", @@ -204,6 +211,7 @@ export function useSimulationWorker(): { workerRef.current = worker; return () => { + console.log("[Petrinaut:Debug] Worker terminated (useEffect cleanup)"); worker.terminate(); }; }, []); @@ -226,10 +234,14 @@ export function useSimulationWorker(): { }) => { // Cancel any pending initialization if (pendingInitRef.current) { + console.warn( + "[Petrinaut:Debug] Cancelling pending initialization - new initialize() called before previous resolved", + ); pendingInitRef.current.reject(new Error("Initialization cancelled")); pendingInitRef.current = null; } + console.log("[Petrinaut:Debug] useSimulationWorker.initialize() called"); setState({ status: "initializing", frames: [], @@ -287,6 +299,7 @@ export function useSimulationWorker(): { }; const reset: WorkerActions["reset"] = () => { + console.log("[Petrinaut:Debug] useSimulationWorker.reset() called"); postMessage({ type: "stop" }); setState(initialState); }; diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index a4dfaf6e076..437141c557f 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -71,16 +71,32 @@ export default defineConfig(({ mode }) => { // Vite lib mode emits worker URLs as `"" + new URL("assets/...", import.meta.url).href` // wrapped in an outer `new URL(...)`. Simplify to `new URL("assets/...", import.meta.url)`. + // Also shim `window` in worker chunks: @babel/standalone bundles the `debug` package + // which accesses `window` in its `useColors()` without a typeof guard. isLibMode && { - name: "simplify-worker-url", + name: "fix-worker-bundle", generateBundle(_, bundle) { - for (const chunk of Object.values(bundle)) { - if (chunk.type === "chunk") { - chunk.code = chunk.code.replace( + for (const [fileName, item] of Object.entries(bundle)) { + if (item.type === "chunk") { + item.code = item.code.replace( /new URL\(\s*\/\* @vite-ignore \*\/\s*"" \+ new URL\(("assets\/[^"]+"), import\.meta\.url\)\.href,\s*import\.meta\.url\s*\)/g, "new URL($1, import.meta.url)", ); } + + // Prepend window shim to worker bundles so libraries that + // access `window` (e.g. debug's useColors in @babel/standalone) + // work in Web Workers + if (fileName.includes("worker")) { + if (item.type === "chunk") { + item.code = `self.window = self;\n${item.code}`; + } else if ( + item.type === "asset" && + typeof item.source === "string" + ) { + item.source = `self.window = self;\n${item.source}`; + } + } } }, }, From a7f2808e1ba127b2ee62c9101b8170497e99236d Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Tue, 17 Feb 2026 11:34:42 +0100 Subject: [PATCH 08/18] Migrate to Vite 8 + TSGo + set bundle external dependencies --- apps/hash-frontend/next.config.js | 14 + libs/@hashintel/petrinaut/assets.d.ts | 1 - libs/@hashintel/petrinaut/package.json | 16 +- libs/@hashintel/petrinaut/tsconfig.json | 6 +- libs/@hashintel/petrinaut/vite.config.ts | 198 +++---- libs/@hashintel/petrinaut/vite.site.config.ts | 28 + yarn.lock | 555 +++++++++++++++++- 7 files changed, 681 insertions(+), 137 deletions(-) delete mode 100644 libs/@hashintel/petrinaut/assets.d.ts create mode 100644 libs/@hashintel/petrinaut/vite.site.config.ts diff --git a/apps/hash-frontend/next.config.js b/apps/hash-frontend/next.config.js index d6d6bef9f1d..606f4cd6a5f 100644 --- a/apps/hash-frontend/next.config.js +++ b/apps/hash-frontend/next.config.js @@ -219,6 +219,20 @@ export default withSentryConfig( // eslint-disable-next-line no-param-reassign webpackConfig.resolve.alias.canvas = false; + if (!isServer) { + // Stub Node.js built-ins for browser — needed by `typescript` (used by + // @hashintel/petrinaut's in-browser language service) + // eslint-disable-next-line no-param-reassign + webpackConfig.resolve.fallback = { + ...webpackConfig.resolve.fallback, + module: false, + fs: false, + path: false, + os: false, + perf_hooks: false, + }; + } + webpackConfig.plugins.push( new DefinePlugin({ __SENTRY_DEBUG__: false, diff --git a/libs/@hashintel/petrinaut/assets.d.ts b/libs/@hashintel/petrinaut/assets.d.ts deleted file mode 100644 index cbe652dbe00..00000000000 --- a/libs/@hashintel/petrinaut/assets.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "*.css"; diff --git a/libs/@hashintel/petrinaut/package.json b/libs/@hashintel/petrinaut/package.json index 8b7a438041c..946f602782d 100644 --- a/libs/@hashintel/petrinaut/package.json +++ b/libs/@hashintel/petrinaut/package.json @@ -25,14 +25,13 @@ "main": "dist/main.js", "types": "dist/main.d.ts", "scripts": { - "build": "vite build --mode=lib", - "build:site": "vite build", - "dev": "vite", + "build": "vite build", + "build:site": "vite build --config vite.site.config.ts", + "dev": "vite --config vite.site.config.ts", "fix:eslint": "eslint --fix .", "lint:eslint": "eslint --report-unused-disable-directives .", - "lint:tsc": "tsc --noEmit", + "lint:tsc": "tsgo --noEmit", "prepublishOnly": "turbo run build", - "preview": "vite preview", "test:unit": "vitest" }, "dependencies": { @@ -54,7 +53,6 @@ "monaco-editor": "0.55.1", "react-icons": "5.5.0", "reactflow": "11.11.4", - "typescript": "5.9.3", "uuid": "13.0.0", "web-worker": "1.4.1" }, @@ -70,15 +68,15 @@ "@types/d3-scale": "4.0.9", "@types/react": "19.2.7", "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "5.0.4", + "@typescript/native-preview": "7.0.0-dev.20260216.1", + "@vitejs/plugin-react": "5.1.4", "babel-plugin-react-compiler": "1.0.0", "eslint": "9.39.2", "immer": "10.1.3", "jsdom": "24.1.3", "react": "19.2.3", "react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "7.1.11", + "vite": "8.0.0-beta.14", "vite-plugin-dts": "4.5.4", "vitest": "4.0.18" }, diff --git a/libs/@hashintel/petrinaut/tsconfig.json b/libs/@hashintel/petrinaut/tsconfig.json index dfc5e99a75e..be6cdd33ea5 100644 --- a/libs/@hashintel/petrinaut/tsconfig.json +++ b/libs/@hashintel/petrinaut/tsconfig.json @@ -3,9 +3,13 @@ "compilerOptions": { "jsx": "react-jsx", "lib": ["dom", "dom.iterable", "ESNext"], + "module": "preserve", "moduleResolution": "bundler", "noEmit": true, - "skipLibCheck": true + "skipLibCheck": true, + "declaration": true, + "emitDeclarationOnly": true, + "isolatedModules": true }, "include": ["src", "demo-site"], "exclude": ["demo-site/dist/**/*"] diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index 437141c557f..e158e511d2c 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -1,134 +1,92 @@ -import path from "node:path"; - import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; +import { defineConfig, esmExternalRequirePlugin } from "vite"; import dts from "vite-plugin-dts"; +import { replacePlugin } from "rolldown/plugins"; -export default defineConfig(({ mode }) => { - const isLibMode = mode === "lib" || !!process.env.VITEST; - - const environment = process.env.VITE_VERCEL_ENV ?? "development"; - const sentryDsn: string | undefined = process.env.SENTRY_DSN; - - return { - root: isLibMode ? undefined : "demo-site", - - // Use relative paths for library assets (fixes worker URL in webpack consumers) - base: isLibMode ? "./" : undefined, - - resolve: { - // Prefer browser exports from packages - // conditions: ["browser", "import", "module"], - alias: { - // Provide browser-safe stubs for Node.js builtins used by TypeScript compiler - os: path.resolve(__dirname, "src/stubs/os.ts"), - fs: path.resolve(__dirname, "src/stubs/node-noop.ts"), - path: path.resolve(__dirname, "src/stubs/node-noop.ts"), - module: path.resolve(__dirname, "src/stubs/node-noop.ts"), - perf_hooks: path.resolve(__dirname, "src/stubs/node-noop.ts"), - }, +/** + * Library build config + */ +export default defineConfig({ + build: { + lib: { + entry: "src/main.ts", + fileName: "main", + formats: ["es"], }, - - build: isLibMode - ? // Library build - { - lib: { - entry: path.resolve(__dirname, "src/main.ts"), - name: "Petrinaut", - fileName: "main", - formats: ["es"], - }, - rollupOptions: { - external: [ - "@hashintel/ds-components", - "@hashintel/ds-helpers", - "elkjs", - "react", - "react-dom", - "reactflow", - ], - output: { - globals: { - react: "React", - "react-dom": "ReactDOM", - }, - }, - }, - sourcemap: true, - emptyOutDir: true, - } - : // Website build - { - outDir: "dist", + rolldownOptions: { + external: [ + "@hashintel/ds-components", + "@hashintel/ds-helpers", + "react", + "react-dom", + "reactflow", + "typescript", + "monaco-editor", + "@babel/standalone", + ], + output: { + globals: { + react: "React", + "react-dom": "ReactDOM", }, + }, + }, + sourcemap: true, + minify: true, + }, - plugins: [ - react({ - babel: { - plugins: ["babel-plugin-react-compiler"], - }, + worker: { + plugins: () => [ + replacePlugin({ + // Consumer Webpack config seem to `define` `typeof window` to `"object"` by default. + // This causes crashes in Web Workers, since `window` is not defined there. + // To prevent this, we do this resolution on our side. + "typeof window": '"undefined"', }), + ], + }, - // Vite lib mode emits worker URLs as `"" + new URL("assets/...", import.meta.url).href` - // wrapped in an outer `new URL(...)`. Simplify to `new URL("assets/...", import.meta.url)`. - // Also shim `window` in worker chunks: @babel/standalone bundles the `debug` package - // which accesses `window` in its `useColors()` without a typeof guard. - isLibMode && { - name: "fix-worker-bundle", - generateBundle(_, bundle) { - for (const [fileName, item] of Object.entries(bundle)) { - if (item.type === "chunk") { - item.code = item.code.replace( - /new URL\(\s*\/\* @vite-ignore \*\/\s*"" \+ new URL\(("assets\/[^"]+"), import\.meta\.url\)\.href,\s*import\.meta\.url\s*\)/g, - "new URL($1, import.meta.url)", - ); - } + plugins: [ + esmExternalRequirePlugin({ + external: [ + "elkjs", + "react/compiler-runtime", + "react/jsx-runtime", + "react/jsx-dev-runtime", + ], + }), - // Prepend window shim to worker bundles so libraries that - // access `window` (e.g. debug's useColors in @babel/standalone) - // work in Web Workers - if (fileName.includes("worker")) { - if (item.type === "chunk") { - item.code = `self.window = self;\n${item.code}`; - } else if ( - item.type === "asset" && - typeof item.source === "string" - ) { - item.source = `self.window = self;\n${item.source}`; - } - } - } - }, + react({ + babel: { + plugins: ["babel-plugin-react-compiler"], }, + }), - isLibMode && - dts({ - rollupTypes: true, - insertTypesEntry: true, - exclude: [ - "**/*.test.*", - "**/*.spec.*", - "playground/**", - "stories/**", - ".storybook/**", - "styled-system/**", - "demo-site/**", - ], - copyDtsFiles: false, - outDir: "dist", - }), - ], + dts({ + rollupTypes: true, + insertTypesEntry: true, + exclude: [ + "**/*.test.*", + "**/*.spec.*", + "playground/**", + "stories/**", + ".storybook/**", + "styled-system/**", + "demo-site/**", + ], + copyDtsFiles: false, + outDir: "dist", + }), + ], - define: { - __ENVIRONMENT__: JSON.stringify(environment), - __SENTRY_DSN__: JSON.stringify(sentryDsn), - // Stub Node.js globals to enable tree-shaking of Node.js-specific codepaths - "process.versions": JSON.stringify({ pnp: undefined }), - "process.platform": JSON.stringify("browser"), - "process.env.NODE_ENV": JSON.stringify("production"), - }, - optimizeDeps: { - include: ["@babel/standalone"], + experimental: { + renderBuiltUrl: (filename) => { + // Fix worker URL for Webpack consumers + // Using `config.base` adds `"" +` prefix to the URL, which breaks the worker URL + if (filename.includes(".worker")) { + return `./${filename}`; + } + return filename; }, - }; + }, }); diff --git a/libs/@hashintel/petrinaut/vite.site.config.ts b/libs/@hashintel/petrinaut/vite.site.config.ts new file mode 100644 index 00000000000..324ba33585e --- /dev/null +++ b/libs/@hashintel/petrinaut/vite.site.config.ts @@ -0,0 +1,28 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +/** Demo site dev server and production build config. */ +export default defineConfig(() => { + const environment = process.env.VITE_VERCEL_ENV ?? "development"; + const sentryDsn: string | undefined = process.env.SENTRY_DSN; + + return { + root: "demo-site", + + define: { + __ENVIRONMENT__: JSON.stringify(environment), + __SENTRY_DSN__: JSON.stringify(sentryDsn), + + // This part could be in the library config + "process.versions": JSON.stringify({ pnp: undefined }), + }, + + plugins: [ + react({ + babel: { + plugins: ["babel-plugin-react-compiler"], + }, + }), + ], + }; +}); diff --git a/yarn.lock b/yarn.lock index 6b84403daf2..3ae063f3d0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3288,6 +3288,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/d34cc504e7765dfb576a663d97067afb614525806b5cad1a5cc1a7183b916fec8ff57fa233585e3926fd5a9e6b31aae6df91aa81ae9775fb7a28f658d3346f0d + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0, @babel/compat-data@npm:^7.28.6": version: 7.28.6 resolution: "@babel/compat-data@npm:7.28.6" @@ -3364,6 +3375,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/core@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-compilation-targets": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.28.6" + "@babel/helpers": "npm:^7.28.6" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/traverse": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/5127d2e8e842ae409e11bcbb5c2dff9874abf5415e8026925af7308e903f4f43397341467a130490d1a39884f461bc2b67f3063bce0be44340db89687fd852aa + languageName: node + linkType: hard + "@babel/eslint-parser@npm:7.28.4": version: 7.28.4 resolution: "@babel/eslint-parser@npm:7.28.4" @@ -3391,6 +3425,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.0": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/349086e6876258ef3fb2823030fee0f6c0eb9c3ebe35fc572e16997f8c030d765f636ddc6299edae63e760ea6658f8ee9a2edfa6d6b24c9a80c917916b973551 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.25.9, @babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": version: 7.27.3 resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" @@ -3603,6 +3650,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/parser@npm:7.29.0" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" @@ -4857,6 +4915,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + debug: "npm:^4.3.1" + checksum: 10c0/f63ef6e58d02a9fbf3c0e2e5f1c877da3e0bc57f91a19d2223d53e356a76859cbaf51171c9211c71816d94a0e69efa2732fd27ffc0e1bbc84b636e60932333eb + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.4.4": version: 7.28.6 resolution: "@babel/types@npm:7.28.6" @@ -4867,6 +4940,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + languageName: node + linkType: hard + "@bcherny/json-schema-ref-parser@npm:10.0.5-fork": version: 10.0.5-fork resolution: "@bcherny/json-schema-ref-parser@npm:10.0.5-fork" @@ -7787,7 +7870,8 @@ __metadata: "@types/d3-scale": "npm:4.0.9" "@types/react": "npm:19.2.7" "@types/react-dom": "npm:19.2.3" - "@vitejs/plugin-react": "npm:5.0.4" + "@typescript/native-preview": "npm:7.0.0-dev.20260216.1" + "@vitejs/plugin-react": "npm:5.1.4" babel-plugin-react-compiler: "npm:1.0.0" d3-array: "npm:3.2.4" d3-scale: "npm:4.0.2" @@ -7800,9 +7884,8 @@ __metadata: react-dom: "npm:19.2.3" react-icons: "npm:5.5.0" reactflow: "npm:11.11.4" - typescript: "npm:5.9.3" uuid: "npm:13.0.0" - vite: "npm:7.1.11" + vite: "npm:8.0.0-beta.14" vite-plugin-dts: "npm:4.5.4" vitest: "npm:4.0.18" web-worker: "npm:1.4.1" @@ -11676,6 +11759,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/runtime@npm:0.113.0": + version: 0.113.0 + resolution: "@oxc-project/runtime@npm:0.113.0" + checksum: 10c0/3a9a3f595986812c83ef5dcb29fa55fc0c8e0e873ab99d95c381b7e54b88f57030cd954a87a24b980dfb9eff5bc2f66426aa86ebdcd7b4dd9773be621517d5a0 + languageName: node + linkType: hard + "@oxc-project/types@npm:=0.103.0": version: 0.103.0 resolution: "@oxc-project/types@npm:0.103.0" @@ -11690,6 +11780,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.113.0": + version: 0.113.0 + resolution: "@oxc-project/types@npm:0.113.0" + checksum: 10c0/a5172494732d6f79eaa73246065e0c5ecfff94900191b930a6bc28f0eaced91b93276a971fdf088db902775ed3d3ea7dca9f45fa2795b69ec8ccc0c35dacaa0d + languageName: node + linkType: hard + "@pandacss/config@npm:1.4.3, @pandacss/config@npm:^1.4.3": version: 1.4.3 resolution: "@pandacss/config@npm:1.4.3" @@ -13767,6 +13864,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.57" @@ -13781,6 +13885,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-x64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.57" @@ -13795,6 +13906,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.57" @@ -13809,6 +13927,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.57" @@ -13823,6 +13948,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.57" @@ -13837,6 +13969,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.57" @@ -13851,6 +13990,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.57" @@ -13865,6 +14011,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.57" @@ -13879,6 +14032,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.57" @@ -13893,6 +14053,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.4" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.57" @@ -13911,6 +14078,15 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.4" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.57" @@ -13925,6 +14101,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.57": version: 1.0.0-beta.57 resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.57" @@ -13939,6 +14122,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rolldown/pluginutils@npm:1.0.0-beta.27": version: 1.0.0-beta.27 resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" @@ -13967,6 +14157,20 @@ __metadata: languageName: node linkType: hard +"@rolldown/pluginutils@npm:1.0.0-rc.3": + version: 1.0.0-rc.3 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.3" + checksum: 10c0/3928b6282a30f307d1b075d2f217180ae173ea9e00638ce46ab65f089bd5f7a0b2c488ae1ce530f509387793c656a2910337c4cd68fa9d37d7e439365989e699 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.4" + checksum: 10c0/c3a4e8c3a4cda43294d04fbaa5066476b933c75b0b5dbee66aaa4ea9bbcf34675dbddd53ffaaf6160b244f1a868a42282aec85a00942fd5a166652f4e2fca192 + languageName: node + linkType: hard + "@rollup/plugin-commonjs@npm:28.0.1": version: 28.0.1 resolution: "@rollup/plugin-commonjs@npm:28.0.1" @@ -19000,6 +19204,87 @@ __metadata: languageName: node linkType: hard +"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260216.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260216.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260216.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260216.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview@npm:7.0.0-dev.20260216.1": + version: 7.0.0-dev.20260216.1 + resolution: "@typescript/native-preview@npm:7.0.0-dev.20260216.1" + dependencies: + "@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20260216.1" + "@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20260216.1" + dependenciesMeta: + "@typescript/native-preview-darwin-arm64": + optional: true + "@typescript/native-preview-darwin-x64": + optional: true + "@typescript/native-preview-linux-arm": + optional: true + "@typescript/native-preview-linux-arm64": + optional: true + "@typescript/native-preview-linux-x64": + optional: true + "@typescript/native-preview-win32-arm64": + optional: true + "@typescript/native-preview-win32-x64": + optional: true + bin: + tsgo: bin/tsgo.js + checksum: 10c0/db461ea3c92cf34f865e37418498aa6af38375af71d747550121468e310f8a1cc3e74467a61c240ad55b33d5c4728dc2be8eb3a1b2ee718d8c783af710df3c68 + languageName: node + linkType: hard + "@typespec/ts-http-runtime@npm:^0.3.0": version: 0.3.1 resolution: "@typespec/ts-http-runtime@npm:0.3.1" @@ -19224,6 +19509,22 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:5.1.4": + version: 5.1.4 + resolution: "@vitejs/plugin-react@npm:5.1.4" + dependencies: + "@babel/core": "npm:^7.29.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-rc.3" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.18.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/dd7b8f40717ecd4a5ab18f467134ea8135f9a443359333d71e4114aeacfc8b679be9fd36dc12290d076c78883a02e708bfe1f0d93411c06c9659da0879b952e3 + languageName: node + linkType: hard + "@vitejs/plugin-react@npm:^4.3.4": version: 4.7.0 resolution: "@vitejs/plugin-react@npm:4.7.0" @@ -25155,7 +25456,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.4": +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": version: 2.0.4 resolution: "detect-libc@npm:2.0.4" checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c @@ -28936,7 +29237,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:13.0.3, glob@npm:^13.0.0": +"glob@npm:13.0.3": version: 13.0.3 resolution: "glob@npm:13.0.3" dependencies: @@ -28995,6 +29296,17 @@ __metadata: languageName: node linkType: hard +"glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + languageName: node + linkType: hard + "glob@npm:^7.0.3, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.6, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -32957,6 +33269,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-android-arm64@npm:1.31.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-arm64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-darwin-arm64@npm:1.25.1" @@ -32964,6 +33283,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-arm64@npm:1.31.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-darwin-x64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-darwin-x64@npm:1.25.1" @@ -32971,6 +33297,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-darwin-x64@npm:1.31.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "lightningcss-freebsd-x64@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-freebsd-x64@npm:1.25.1" @@ -32978,6 +33311,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-freebsd-x64@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-freebsd-x64@npm:1.31.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "lightningcss-linux-arm-gnueabihf@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm-gnueabihf@npm:1.25.1" @@ -32985,6 +33325,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm-gnueabihf@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.31.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "lightningcss-linux-arm64-gnu@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm64-gnu@npm:1.25.1" @@ -32992,6 +33339,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-arm64-musl@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-arm64-musl@npm:1.25.1" @@ -32999,6 +33353,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-arm64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.31.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "lightningcss-linux-x64-gnu@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-x64-gnu@npm:1.25.1" @@ -33006,6 +33367,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-gnu@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "lightningcss-linux-x64-musl@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-linux-x64-musl@npm:1.25.1" @@ -33013,6 +33381,20 @@ __metadata: languageName: node linkType: hard +"lightningcss-linux-x64-musl@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-linux-x64-musl@npm:1.31.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "lightningcss-win32-x64-msvc@npm:1.25.1": version: 1.25.1 resolution: "lightningcss-win32-x64-msvc@npm:1.25.1" @@ -33020,6 +33402,13 @@ __metadata: languageName: node linkType: hard +"lightningcss-win32-x64-msvc@npm:1.31.1": + version: 1.31.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.31.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "lightningcss@npm:1.25.1": version: 1.25.1 resolution: "lightningcss@npm:1.25.1" @@ -33057,6 +33446,49 @@ __metadata: languageName: node linkType: hard +"lightningcss@npm:^1.31.1": + version: 1.31.1 + resolution: "lightningcss@npm:1.31.1" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.31.1" + lightningcss-darwin-arm64: "npm:1.31.1" + lightningcss-darwin-x64: "npm:1.31.1" + lightningcss-freebsd-x64: "npm:1.31.1" + lightningcss-linux-arm-gnueabihf: "npm:1.31.1" + lightningcss-linux-arm64-gnu: "npm:1.31.1" + lightningcss-linux-arm64-musl: "npm:1.31.1" + lightningcss-linux-x64-gnu: "npm:1.31.1" + lightningcss-linux-x64-musl: "npm:1.31.1" + lightningcss-win32-arm64-msvc: "npm:1.31.1" + lightningcss-win32-x64-msvc: "npm:1.31.1" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/c6754b305d4a73652e472fc0d7d65384a6e16c336ea61068eca60de2a45bd5c30abbf012358b82eac56ee98b5d88028932cda5268ff61967cffa400b9e7ee2ba + languageName: node + linkType: hard + "lilconfig@npm:^3.1.3": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" @@ -38890,7 +39322,7 @@ __metadata: languageName: node linkType: hard -"react-refresh@npm:0.18.0": +"react-refresh@npm:0.18.0, react-refresh@npm:^0.18.0": version: 0.18.0 resolution: "react-refresh@npm:0.18.0" checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2 @@ -40169,6 +40601,58 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "rolldown@npm:1.0.0-rc.4" + dependencies: + "@oxc-project/types": "npm:=0.113.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.4" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.4" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.4" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.4" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.4" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.4" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.4" + "@rolldown/pluginutils": "npm:1.0.0-rc.4" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/1336aedae71b5885036aeff6bbe9d6c58af205f72e8686edb2a3b27e6b4eb056f2f528b50373ad91567696369501f5b18eb99a926343bdb3012b7495962ddcfb + languageName: node + linkType: hard + "rollup@npm:4.55.3, rollup@npm:^4.34.9, rollup@npm:^4.35.0, rollup@npm:^4.43.0": version: 4.55.3 resolution: "rollup@npm:4.55.3" @@ -44630,6 +45114,65 @@ __metadata: languageName: node linkType: hard +"vite@npm:8.0.0-beta.14": + version: 8.0.0-beta.14 + resolution: "vite@npm:8.0.0-beta.14" + dependencies: + "@oxc-project/runtime": "npm:0.113.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.31.1" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rolldown: "npm:1.0.0-rc.4" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.0.0-alpha.31 + esbuild: ^0.27.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/e00e30071444a419f4cd35775af64c2fce5208a95eb05d50c9f646d6c32962bb0aa969dd07d44bc284f7c145846eaea98994864fd5100f45e7432db59e322c38 + languageName: node + linkType: hard + "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^6.0.0 || ^7.0.0": version: 7.1.12 resolution: "vite@npm:7.1.12" From c08c14312c7818bd50e72fa5114d1d5a3c481015 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Tue, 17 Feb 2026 11:34:42 +0100 Subject: [PATCH 09/18] Yarn config update --- yarn.config.cjs | 5 ++- yarn.lock | 95 ++++--------------------------------------------- 2 files changed, 10 insertions(+), 90 deletions(-) diff --git a/yarn.config.cjs b/yarn.config.cjs index 043302f9e3c..6c5e2f9faa4 100644 --- a/yarn.config.cjs +++ b/yarn.config.cjs @@ -17,7 +17,10 @@ const enforcedDevDependencies = { const ignoredDependencies = [ "@sentry/webpack-plugin", - // Petrinaut SDCPN uses multiple packages which are many versions behind in other workspaces + // Petrinaut uses Vite 8 + "vite", + "@vitejs/plugin-react", + // Petrinaut uses multiple packages which are many versions behind in other workspaces // To be un-ignored once H-5639 completed "vitest", "@dnd-kit/sortable", diff --git a/yarn.lock b/yarn.lock index 3ae063f3d0c..ee3306a4567 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3277,18 +3277,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/code-frame@npm:7.28.6" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.28.5" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.1.1" - checksum: 10c0/ed5d57f99455e3b1c23e75ebb8430c6b9800b4ecd0121b4348b97cecb65406a47778d6db61f0d538a4958bb01b4b277e90348a68d39bd3beff1d7c940ed6dd66 - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.29.0": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": version: 7.29.0 resolution: "@babel/code-frame@npm:7.29.0" dependencies: @@ -3352,30 +3341,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.18.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.0, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4": - version: 7.28.6 - resolution: "@babel/core@npm:7.28.6" - dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/generator": "npm:^7.28.6" - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-module-transforms": "npm:^7.28.6" - "@babel/helpers": "npm:^7.28.6" - "@babel/parser": "npm:^7.28.6" - "@babel/template": "npm:^7.28.6" - "@babel/traverse": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - "@jridgewell/remapping": "npm:^2.3.5" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/716b88b1ab057aa53ffa40f2b2fb7e4ab7a35cd6a065fa60e55ca13d2a666672592329f7ea9269aec17e90cc7ce29f42eda566d07859bfd998329a9f283faadb - languageName: node - linkType: hard - -"@babel/core@npm:^7.29.0": +"@babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.18.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.0, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4, @babel/core@npm:^7.29.0": version: 7.29.0 resolution: "@babel/core@npm:7.29.0" dependencies: @@ -3412,20 +3378,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.26.3, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.28.5, @babel/generator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/generator@npm:7.28.6" - dependencies: - "@babel/parser": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" - jsesc: "npm:^3.0.2" - checksum: 10c0/162fa358484a9a18e8da1235d998f10ea77c63bab408c8d3e327d5833f120631a77ff022c5ed1d838ee00523f8bb75df1f08196d3657d0bca9f2cfeb8503cc12 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.29.0": +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.18.13, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.26.3, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.28.5, @babel/generator@npm:^7.29.0": version: 7.29.1 resolution: "@babel/generator@npm:7.29.1" dependencies: @@ -3639,18 +3592,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/parser@npm:7.28.6" - dependencies: - "@babel/types": "npm:^7.28.6" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/d6bfe8aa8e067ef58909e9905496157312372ca65d8d2a4f2b40afbea48d59250163755bba8ae626a615da53d192b084bcfc8c9dad8b01e315b96967600de581 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.29.0": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.16.8, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5, @babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": version: 7.29.0 resolution: "@babel/parser@npm:7.29.0" dependencies: @@ -4900,22 +4842,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/traverse@npm:7.28.6" - dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/generator": "npm:^7.28.6" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.6" - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - debug: "npm:^4.3.1" - checksum: 10c0/ed5deb9c3f03e2d1ad2d44b9c92c84cce24593245c3f7871ce27ee1b36d98034e6cd895fa98a94eb44ebabe1d22f51b10b09432939d1c51a0fcaab98f17a97bc - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.29.0": +"@babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": version: 7.29.0 resolution: "@babel/traverse@npm:7.29.0" dependencies: @@ -4930,17 +4857,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.4.4": - version: 7.28.6 - resolution: "@babel/types@npm:7.28.6" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/54a6a9813e48ef6f35aa73c03b3c1572cad7fa32b61b35dd07e4230bc77b559194519c8a4d8106a041a27cc7a94052579e238a30a32d5509aa4da4d6fd83d990 - languageName: node - linkType: hard - -"@babel/types@npm:^7.29.0": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6, @babel/types@npm:^7.26.0, @babel/types@npm:^7.26.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": version: 7.29.0 resolution: "@babel/types@npm:7.29.0" dependencies: From 4e7058dac556fcdae0039eccabd3c6747480c114 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Tue, 17 Feb 2026 11:34:43 +0100 Subject: [PATCH 10/18] Add back TypeScript dependency as used by Petrinaut at runtime --- libs/@hashintel/petrinaut/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/libs/@hashintel/petrinaut/package.json b/libs/@hashintel/petrinaut/package.json index 946f602782d..f7d5a4ce172 100644 --- a/libs/@hashintel/petrinaut/package.json +++ b/libs/@hashintel/petrinaut/package.json @@ -53,6 +53,7 @@ "monaco-editor": "0.55.1", "react-icons": "5.5.0", "reactflow": "11.11.4", + "typescript": "5.9.3", "uuid": "13.0.0", "web-worker": "1.4.1" }, diff --git a/yarn.lock b/yarn.lock index ee3306a4567..e9379634301 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7801,6 +7801,7 @@ __metadata: react-dom: "npm:19.2.3" react-icons: "npm:5.5.0" reactflow: "npm:11.11.4" + typescript: "npm:5.9.3" uuid: "npm:13.0.0" vite: "npm:8.0.0-beta.14" vite-plugin-dts: "npm:4.5.4" From 86b22b784588cbb41dbb1ff94a53a59c67a91ef8 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Tue, 17 Feb 2026 11:34:43 +0100 Subject: [PATCH 11/18] Autonomous tsconfig.json to be compatible with TSGo --- libs/@hashintel/petrinaut/eslint.config.js | 2 +- libs/@hashintel/petrinaut/src/playback/provider.tsx | 10 ---------- .../petrinaut/src/simulation/provider.tsx | 8 -------- .../src/simulation/worker/use-simulation-worker.ts | 13 ------------- libs/@hashintel/petrinaut/tsconfig.json | 8 +++++++- libs/@hashintel/petrinaut/vite.config.ts | 3 ++- 6 files changed, 10 insertions(+), 34 deletions(-) diff --git a/libs/@hashintel/petrinaut/eslint.config.js b/libs/@hashintel/petrinaut/eslint.config.js index 57cf07fa2b9..8a36311150a 100644 --- a/libs/@hashintel/petrinaut/eslint.config.js +++ b/libs/@hashintel/petrinaut/eslint.config.js @@ -7,10 +7,10 @@ export default [ parserOptions: { projectService: { allowDefaultProject: [ - "assets.d.ts", "panda.config.ts", "postcss.config.cjs", "vite.config.ts", + "vite.site.config.ts", ], }, tsconfigRootDir: import.meta.dirname, diff --git a/libs/@hashintel/petrinaut/src/playback/provider.tsx b/libs/@hashintel/petrinaut/src/playback/provider.tsx index e249729b252..c66306e6666 100644 --- a/libs/@hashintel/petrinaut/src/playback/provider.tsx +++ b/libs/@hashintel/petrinaut/src/playback/provider.tsx @@ -414,10 +414,6 @@ export const PlaybackProvider: React.FC = ({ const play: PlaybackContextValue["play"] = async () => { const simState = simulationStateRef.current; - console.log( - "[Petrinaut:Debug] PlaybackProvider.play() called, simState:", - simState, - ); const state = stateValuesRef.current; const { maxFramesAhead, batchSize } = getPlayModeBackpressure( state.playMode, @@ -425,9 +421,6 @@ export const PlaybackProvider: React.FC = ({ // Initialize simulation if not run yet if (simState === "NotRun") { - console.log( - "[Petrinaut:Debug] PlaybackProvider.play() -> calling initialize (simState was NotRun)", - ); await initialize({ seed: Date.now(), dt: dtRef.current, @@ -436,9 +429,6 @@ export const PlaybackProvider: React.FC = ({ }); // Initialization complete - start simulation // The effect will set playbackState to "Playing" when simulation starts running - console.log( - "[Petrinaut:Debug] PlaybackProvider.play() -> initialization complete, calling runSimulation()", - ); runSimulation(); return; } diff --git a/libs/@hashintel/petrinaut/src/simulation/provider.tsx b/libs/@hashintel/petrinaut/src/simulation/provider.tsx index 0bb0987142e..b8cb07e7050 100644 --- a/libs/@hashintel/petrinaut/src/simulation/provider.tsx +++ b/libs/@hashintel/petrinaut/src/simulation/provider.tsx @@ -101,11 +101,6 @@ export const SimulationProvider: React.FC = ({ // Reinitialize when petriNetId changes useEffect(() => { - console.log( - "[Petrinaut:Debug] SimulationProvider: petriNetId changed to", - petriNetId, - "- calling reset()", - ); workerActions.reset(); setStateValues(INITIAL_STATE_VALUES); }, [petriNetId, workerActions]); @@ -160,9 +155,6 @@ export const SimulationProvider: React.FC = ({ // Delegate to worker (maxTime is immutable once set at initialization) // Returns a promise that resolves when initialization is complete - console.log( - "[Petrinaut:Debug] SimulationProvider.initialize() called, delegating to worker", - ); return workerActions.initialize({ sdcpn, initialMarking: currentState.initialMarking, diff --git a/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts b/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts index b2fffbb75f2..7bb4d86b847 100644 --- a/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts +++ b/libs/@hashintel/petrinaut/src/simulation/worker/use-simulation-worker.ts @@ -137,9 +137,6 @@ export function useSimulationWorker(): { switch (message.type) { case "ready": - console.log( - "[Petrinaut:Debug] Worker sent 'ready' message, resolving pending init", - ); setState((prev) => ({ ...prev, status: prev.status === "initializing" ? "ready" : prev.status, @@ -180,10 +177,6 @@ export function useSimulationWorker(): { break; case "error": - console.error( - "[Petrinaut:Debug] Worker sent 'error' message:", - message.message, - ); setState((prev) => ({ ...prev, status: "error", @@ -211,7 +204,6 @@ export function useSimulationWorker(): { workerRef.current = worker; return () => { - console.log("[Petrinaut:Debug] Worker terminated (useEffect cleanup)"); worker.terminate(); }; }, []); @@ -234,14 +226,10 @@ export function useSimulationWorker(): { }) => { // Cancel any pending initialization if (pendingInitRef.current) { - console.warn( - "[Petrinaut:Debug] Cancelling pending initialization - new initialize() called before previous resolved", - ); pendingInitRef.current.reject(new Error("Initialization cancelled")); pendingInitRef.current = null; } - console.log("[Petrinaut:Debug] useSimulationWorker.initialize() called"); setState({ status: "initializing", frames: [], @@ -299,7 +287,6 @@ export function useSimulationWorker(): { }; const reset: WorkerActions["reset"] = () => { - console.log("[Petrinaut:Debug] useSimulationWorker.reset() called"); postMessage({ type: "stop" }); setState(initialState); }; diff --git a/libs/@hashintel/petrinaut/tsconfig.json b/libs/@hashintel/petrinaut/tsconfig.json index be6cdd33ea5..82a6a739f89 100644 --- a/libs/@hashintel/petrinaut/tsconfig.json +++ b/libs/@hashintel/petrinaut/tsconfig.json @@ -1,10 +1,16 @@ { - "extends": "@local/tsconfig/legacy-base-tsconfig-to-refactor.json", "compilerOptions": { "jsx": "react-jsx", + "target": "es2024", "lib": ["dom", "dom.iterable", "ESNext"], "module": "preserve", "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, "noEmit": true, "skipLibCheck": true, "declaration": true, diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index e158e511d2c..be30846a693 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -1,7 +1,8 @@ import react from "@vitejs/plugin-react"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { replacePlugin } from "rolldown/plugins"; import { defineConfig, esmExternalRequirePlugin } from "vite"; import dts from "vite-plugin-dts"; -import { replacePlugin } from "rolldown/plugins"; /** * Library build config From 9f5043a099e2edf266fb69ef2c9d8aa1003a5129 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Tue, 17 Feb 2026 12:08:13 +0100 Subject: [PATCH 12/18] Remove stubs --- .../petrinaut/src/stubs/node-noop.ts | 36 ------------------- libs/@hashintel/petrinaut/src/stubs/os.ts | 4 --- 2 files changed, 40 deletions(-) delete mode 100644 libs/@hashintel/petrinaut/src/stubs/node-noop.ts delete mode 100644 libs/@hashintel/petrinaut/src/stubs/os.ts diff --git a/libs/@hashintel/petrinaut/src/stubs/node-noop.ts b/libs/@hashintel/petrinaut/src/stubs/node-noop.ts deleted file mode 100644 index 28149dbd29d..00000000000 --- a/libs/@hashintel/petrinaut/src/stubs/node-noop.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ - -/** - * Proxy-based noop stub for Node.js built-in modules (os, fs, path, etc.). - * - * Any property access returns another proxy, any function call returns "". - * This prevents TypeScript compiler and other Node.js-dependent code from - * throwing when their Node.js API calls are unreachable in the browser. - */ - -type Noop = ((...args: unknown[]) => unknown) & Record; - -const handler: ProxyHandler = { - get(_target, prop) { - if (prop === Symbol.toPrimitive) { - return () => ""; - } - if (prop === Symbol.toStringTag) { - return "Module"; - } - if (prop === "__esModule") { - return true; - } - if (prop === "default") { - return noopProxy; - } - return noopProxy; - }, - apply() { - return ""; - }, -}; - -const noopProxy: Noop = new Proxy((() => "") as Noop, handler); - -export default noopProxy; diff --git a/libs/@hashintel/petrinaut/src/stubs/os.ts b/libs/@hashintel/petrinaut/src/stubs/os.ts deleted file mode 100644 index 0aac5fbe1b4..00000000000 --- a/libs/@hashintel/petrinaut/src/stubs/os.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** Browser stub for Node.js `os` module. */ -export const platform = () => "browser"; -export const EOL = "\n"; -export default { platform, EOL }; From ade500605e50c875cb5c25e0fb9e42d4db473083 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 19 Feb 2026 00:04:44 +0100 Subject: [PATCH 13/18] Allow Monaco Editor CDN resources in Content Security Policy Monaco Editor (via @monaco-editor/react used by petrinaut) loads scripts and styles from cdn.jsdelivr.net and embeds icon fonts as base64 data URIs. Co-Authored-By: Claude Opus 4.6 --- apps/hash-frontend/src/lib/csp.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/hash-frontend/src/lib/csp.ts b/apps/hash-frontend/src/lib/csp.ts index 4b327029a49..f264045eb8f 100644 --- a/apps/hash-frontend/src/lib/csp.ts +++ b/apps/hash-frontend/src/lib/csp.ts @@ -24,6 +24,8 @@ export const buildCspHeader = (nonce: string): string => { "https://apis.google.com", // Vercel toolbar / live preview widget "https://vercel.live", + // Monaco Editor loaded from CDN by @monaco-editor/react (used by petrinaut) + "https://cdn.jsdelivr.net", ], "style-src": [ @@ -31,6 +33,8 @@ export const buildCspHeader = (nonce: string): string => { // Required for Emotion/MUI CSS-in-JS inline style injection. // @todo Use nonce-based approach via Emotion's cache `nonce` option. "'unsafe-inline'", + // Monaco Editor stylesheet loaded from CDN by @monaco-editor/react (used by petrinaut) + "https://cdn.jsdelivr.net", ], "img-src": [ @@ -45,7 +49,11 @@ export const buildCspHeader = (nonce: string): string => { ...(process.env.NODE_ENV === "development" ? ["http:"] : []), ], - "font-src": ["'self'"], + "font-src": [ + "'self'", + // Monaco Editor CSS embeds the Codicon icon font as an inline base64 data URI + "data:", + ], "connect-src": [ "'self'", From 047a630a504ad03f898f38dd1f4801fba7260919 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 19 Feb 2026 00:28:52 +0100 Subject: [PATCH 14/18] Remove old format conversion, save/load SDCPN directly Delete convert-net-formats.ts and simplify save/load hooks to persist SDCPN format directly instead of converting to/from the old PetriNetDefinitionObject format. This resolves lossy conversion issues (arc weights, initial token counts, synthetic lambda code, incomplete format detection). Co-Authored-By: Claude Opus 4.6 --- .../convert-net-formats.ts | 289 ------------------ .../use-process-save-and-load.tsx | 13 +- .../use-persisted-nets.ts | 22 +- yarn.lock | 13 +- 4 files changed, 5 insertions(+), 332 deletions(-) delete mode 100644 apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts deleted file mode 100644 index 0c6bf0e525b..00000000000 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/convert-net-formats.ts +++ /dev/null @@ -1,289 +0,0 @@ -import type { Color, Place, SDCPN, Transition } from "@hashintel/petrinaut"; - -/** - * Old format types from petrinaut-old. - * Defined here to avoid importing from the old package. - */ -type TokenCounts = { - [tokenTypeId: string]: number; -}; - -type ArcData = { - tokenWeights: { - [tokenTypeId: string]: number | undefined; - }; -}; - -type ArcType = { - id: string; - source: string; - target: string; - data?: ArcData; -}; - -type PlaceNodeData = { - label: string; - initialTokenCounts?: TokenCounts; - parentNetNode?: { - id: string; - type: "input" | "output"; - }; - type: "place"; -}; - -type TransitionNodeData = { - conditions?: { - id: string; - name: string; - probability: number; - outputEdgeId: string; - }[]; - label: string; - delay?: number; - description?: string; - childNet?: { - childNetId: string; - childNetTitle: string; - inputPlaceIds: string[]; - outputPlaceIds: string[]; - }; - type: "transition"; -}; - -type NodeType = { - id: string; - type?: string; - position: { x: number; y: number }; - width?: number; - height?: number; - data: PlaceNodeData | TransitionNodeData; -}; - -type TokenType = { - id: string; - name: string; - color: string; -}; - -export type PetriNetDefinitionObject = { - arcs: ArcType[]; - nodes: NodeType[]; - tokenTypes: TokenType[]; -}; - -/** - * Convert old PetriNetDefinitionObject format to new SDCPN format. - * Used when loading persisted data from the graph. - */ -export function convertPetriNetDefinitionObjectToSDCPN( - old: PetriNetDefinitionObject, -): SDCPN { - const places: Place[] = []; - const transitions: Transition[] = []; - - // Separate nodes into places and transitions - for (const node of old.nodes) { - if (node.data.type === "place") { - const placeData = node.data as PlaceNodeData; - places.push({ - id: node.id, - name: placeData.label, - colorId: null, - dynamicsEnabled: false, - differentialEquationId: null, - x: node.position.x, - y: node.position.y, - width: node.width, - height: node.height, - }); - } else if (node.data.type === "transition") { - const transitionData = node.data as TransitionNodeData; - - // Find input arcs (arcs where this transition is the target) - const inputArcs = old.arcs - .filter((arc) => arc.target === node.id) - .map((arc) => ({ - placeId: arc.source, - weight: getArcWeight(arc), - })); - - // Find output arcs (arcs where this transition is the source) - const outputArcs = old.arcs - .filter((arc) => arc.source === node.id) - .map((arc) => ({ - placeId: arc.target, - weight: getArcWeight(arc), - })); - - transitions.push({ - id: node.id, - name: transitionData.label, - inputArcs, - outputArcs, - lambdaType: "predicate", - lambdaCode: "return true;", - transitionKernelCode: "return input;", - x: node.position.x, - y: node.position.y, - width: node.width, - height: node.height, - }); - } - } - - // Convert token types to colors - const types: Color[] = old.tokenTypes.map((tokenType) => ({ - id: tokenType.id, - name: tokenType.name, - iconSlug: "circle", - displayColor: tokenType.color, - elements: [], - })); - - return { - places, - transitions, - types, - differentialEquations: [], - parameters: [], - }; -} - -/** - * Convert new SDCPN format to old PetriNetDefinitionObject format. - * Used when saving to the graph to maintain backward compatibility. - */ -export function convertSDCPNToPetriNetDefinitionObject( - sdcpn: SDCPN, -): PetriNetDefinitionObject { - const nodes: NodeType[] = []; - const arcs: ArcType[] = []; - - // Convert places to nodes - for (const place of sdcpn.places) { - nodes.push({ - id: place.id, - type: "place", - position: { x: place.x, y: place.y }, - width: place.width, - height: place.height, - data: { - label: place.name, - type: "place", - }, - }); - } - - // Convert transitions to nodes and extract arcs - let arcIdCounter = 0; - for (const transition of sdcpn.transitions) { - nodes.push({ - id: transition.id, - type: "transition", - position: { x: transition.x, y: transition.y }, - width: transition.width, - height: transition.height, - data: { - label: transition.name, - type: "transition", - }, - }); - - // Convert input arcs (place → transition) - for (const inputArc of transition.inputArcs) { - arcs.push({ - id: `arc-${arcIdCounter++}`, - source: inputArc.placeId, - target: transition.id, - data: { - tokenWeights: createTokenWeights(inputArc.weight, sdcpn.types), - }, - }); - } - - // Convert output arcs (transition → place) - for (const outputArc of transition.outputArcs) { - arcs.push({ - id: `arc-${arcIdCounter++}`, - source: transition.id, - target: outputArc.placeId, - data: { - tokenWeights: createTokenWeights(outputArc.weight, sdcpn.types), - }, - }); - } - } - - // Convert colors to token types - const tokenTypes: TokenType[] = sdcpn.types.map((color) => ({ - id: color.id, - name: color.name, - color: color.displayColor, - })); - - return { - arcs, - nodes, - tokenTypes, - }; -} - -/** - * Helper to get the total weight from an arc's token weights. - * Returns 1 if no weights are defined. - */ -function getArcWeight(arc: ArcType): number { - if (!arc.data?.tokenWeights) { - return 1; - } - const weights = Object.values(arc.data.tokenWeights).filter( - (w): w is number => w !== undefined, - ); - return weights.length > 0 ? Math.max(...weights) : 1; -} - -/** - * Helper to create token weights object from a single weight value. - * Distributes the weight to the first token type if available. - */ -function createTokenWeights( - weight: number, - types: Color[], -): { [tokenTypeId: string]: number | undefined } { - const firstType = types[0]; - if (!firstType) { - return {}; - } - // Apply weight to first token type - return { [firstType.id]: weight }; -} - -/** - * Check if an object is in the old PetriNetDefinitionObject format. - */ -export function isOldFormat(obj: unknown): obj is PetriNetDefinitionObject { - if (typeof obj !== "object" || obj === null) { - return false; - } - const record = obj as Record; - return ( - Array.isArray(record.arcs) && - Array.isArray(record.nodes) && - Array.isArray(record.tokenTypes) - ); -} - -/** - * Check if an object is in the new SDCPN format. - */ -export function isSDCPNFormat(obj: unknown): obj is SDCPN { - if (typeof obj !== "object" || obj === null) { - return false; - } - const record = obj as Record; - return ( - Array.isArray(record.places) && - Array.isArray(record.transitions) && - Array.isArray(record.types) - ); -} diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx index 78bdc4dfa30..79b01dc4dad 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx @@ -30,10 +30,6 @@ import { updateEntityMutation, } from "../../../graphql/queries/knowledge/entity.queries"; import { useActiveWorkspace } from "../../shared/workspace-context"; -import { - convertSDCPNToPetriNetDefinitionObject, - type PetriNetDefinitionObject, -} from "./convert-net-formats"; import { getPersistedNetsFromSubgraph, usePersistedNets, @@ -193,10 +189,6 @@ export const useProcessSaveAndLoad = ({ setPersistPending(true); - // Convert SDCPN to old format for persistence (backward compatibility) - const oldFormatDefinition: PetriNetDefinitionObject = - convertSDCPNToPetriNetDefinitionObject(petriNet); - let persistedEntityId = selectedNetId; if (selectedNetId) { @@ -210,12 +202,11 @@ export const useProcessSaveAndLoad = ({ path: [ systemPropertyTypes.definitionObject.propertyTypeBaseUrl, ], - // @ts-expect-error -- PetriNetDefinitionObject not assignable to PropertyWithMetadata property: { metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - value: oldFormatDefinition, + value: petriNet, }, }, { @@ -243,7 +234,7 @@ export const useProcessSaveAndLoad = ({ metadata: { dataTypeId: blockProtocolDataTypes.object.dataTypeId, }, - value: oldFormatDefinition, + value: petriNet, }, "https://hash.ai/@h/types/property-type/title/": { metadata: { diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts index 85d57e2f63a..35a09c77203 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load/use-persisted-nets.ts @@ -1,5 +1,6 @@ import { useQuery } from "@apollo/client"; import { getRoots } from "@blockprotocol/graph/stdlib"; +import type { SDCPN } from "@hashintel/petrinaut"; import { deserializeQueryEntitySubgraphResponse } from "@local/hash-graph-sdk/entity"; import { currentTimeInstantTemporalAxes } from "@local/hash-isomorphic-utils/graph-queries"; import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; @@ -11,12 +12,6 @@ import type { QueryEntitySubgraphQueryVariables, } from "../../../../graphql/api-types.gen"; import { queryEntitySubgraphQuery } from "../../../../graphql/queries/knowledge/entity.queries"; -import { - convertPetriNetDefinitionObjectToSDCPN, - isOldFormat, - isSDCPNFormat, - type PetriNetDefinitionObject, -} from "../convert-net-formats"; import type { PersistedNet } from "../use-process-save-and-load"; export const getPersistedNetsFromSubgraph = ( @@ -37,20 +32,7 @@ export const getPersistedNetsFromSubgraph = ( "https://hash.ai/@h/types/property-type/definition-object/" ]; - // Convert from old format to SDCPN if needed - let definition; - if (isOldFormat(rawDefinition)) { - definition = convertPetriNetDefinitionObjectToSDCPN( - rawDefinition as PetriNetDefinitionObject, - ); - } else if (isSDCPNFormat(rawDefinition)) { - definition = rawDefinition; - } else { - // Fallback: treat as old format and attempt conversion - definition = convertPetriNetDefinitionObjectToSDCPN( - rawDefinition as PetriNetDefinitionObject, - ); - } + const definition = rawDefinition as SDCPN; const userEditable = !!data.queryEntitySubgraph.entityPermissions?.[net.entityId]?.update; diff --git a/yarn.lock b/yarn.lock index e9379634301..43c23acf4c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29155,7 +29155,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:13.0.3": +"glob@npm:13.0.3, glob@npm:^13.0.0": version: 13.0.3 resolution: "glob@npm:13.0.3" dependencies: @@ -29214,17 +29214,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^13.0.0": - version: 13.0.0 - resolution: "glob@npm:13.0.0" - dependencies: - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - path-scurry: "npm:^2.0.0" - checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a - languageName: node - linkType: hard - "glob@npm:^7.0.3, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.6, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" From 82d71fe5ae43a4f119882026099500172aef1588 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 19 Feb 2026 01:58:09 +0100 Subject: [PATCH 15/18] Disable LightningCSS minification to preserve backdrop-filter Vite 8 defaults to LightningCSS for CSS minification, which strips the standard `backdrop-filter` property based on browser-target heuristics, leaving only the `-webkit-` prefixed version. https://github.com/parcel-bundler/lightningcss/issues/695 Co-Authored-By: Claude Opus 4.6 --- libs/@hashintel/petrinaut/vite.config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/@hashintel/petrinaut/vite.config.ts b/libs/@hashintel/petrinaut/vite.config.ts index be30846a693..744ed2004b9 100644 --- a/libs/@hashintel/petrinaut/vite.config.ts +++ b/libs/@hashintel/petrinaut/vite.config.ts @@ -34,6 +34,11 @@ export default defineConfig({ }, sourcemap: true, minify: true, + // Use esbuild for CSS minification. Vite 8 defaults to LightningCSS which + // strips the standard `backdrop-filter` in favour of `-webkit-backdrop-filter` + // based on its browser-target heuristics. + // https://github.com/parcel-bundler/lightningcss/issues/695 + cssMinify: false, }, worker: { From 65e28e38b437dddc97fd08f66ece800f44c5e300 Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 19 Feb 2026 02:07:46 +0100 Subject: [PATCH 16/18] Fix minimap positioning offset Co-Authored-By: Claude Opus 4.6 --- .../petrinaut/src/views/SDCPN/components/mini-map.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/components/mini-map.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/components/mini-map.tsx index 9bbabe19fbc..2d493ca2405 100644 --- a/libs/@hashintel/petrinaut/src/views/SDCPN/components/mini-map.tsx +++ b/libs/@hashintel/petrinaut/src/views/SDCPN/components/mini-map.tsx @@ -83,9 +83,10 @@ export const MiniMap: React.FC> = (props) => { const { selectedResourceId, propertiesPanelWidth } = use(EditorContext); const isPropertiesPanelVisible = selectedResourceId !== null; - const rightOffset = isPropertiesPanelVisible - ? propertiesPanelWidth + PANEL_MARGIN * 2 - : PANEL_MARGIN; + const minimapOffset = 12; + const panelOffset = isPropertiesPanelVisible + ? propertiesPanelWidth + PANEL_MARGIN + : 0; return ( > = (props) => { ariaLabel="" className={miniMapClassName} style={{ - top: 0, - right: rightOffset, + top: minimapOffset, + right: minimapOffset + panelOffset, bottom: "auto", left: "auto", width: 130, From c1a9fe20b40f10167f0fb869c0a687ab68f5480f Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Thu, 19 Feb 2026 10:52:03 +0100 Subject: [PATCH 17/18] Add isSDCPNEqual for structural comparison of SDCPN definitions Replace the field-by-field JSON.stringify isDirty check with a typed isSDCPNEqual function that recursively deep-compares two SDCPN objects. This automatically stays correct if SDCPN gains new fields. Co-Authored-By: Claude Opus 4.6 --- .../use-process-save-and-load.tsx | 59 +-------- .../petrinaut/src/lib/deep-equal.test.ts | 117 ++++++++++++++++++ .../petrinaut/src/lib/deep-equal.ts | 74 +++++++++++ libs/@hashintel/petrinaut/src/petrinaut.tsx | 2 + 4 files changed, 198 insertions(+), 54 deletions(-) create mode 100644 libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts create mode 100644 libs/@hashintel/petrinaut/src/lib/deep-equal.ts diff --git a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx index 79b01dc4dad..1ab41904447 100644 --- a/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx +++ b/apps/hash-frontend/src/pages/process.page/process-editor-wrapper/use-process-save-and-load.tsx @@ -4,6 +4,7 @@ import type { PropertyObjectWithMetadata, } from "@blockprotocol/type-system"; import type { SDCPN } from "@hashintel/petrinaut"; +import { isSDCPNEqual } from "@hashintel/petrinaut"; import { HashEntity } from "@local/hash-graph-sdk/entity"; import { blockProtocolDataTypes, @@ -95,60 +96,10 @@ export const useProcessSaveAndLoad = ({ return true; } - if (title !== persistedNet.title) { - return true; - } - - if (petriNet.places.length !== persistedNet.definition.places.length) { - return true; - } - - if ( - petriNet.transitions.length !== persistedNet.definition.transitions.length - ) { - return true; - } - - if (petriNet.types.length !== persistedNet.definition.types.length) { - return true; - } - - if ( - JSON.stringify(petriNet.places) !== - JSON.stringify(persistedNet.definition.places) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.transitions) !== - JSON.stringify(persistedNet.definition.transitions) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.types) !== - JSON.stringify(persistedNet.definition.types) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.differentialEquations) !== - JSON.stringify(persistedNet.definition.differentialEquations) - ) { - return true; - } - - if ( - JSON.stringify(petriNet.parameters) !== - JSON.stringify(persistedNet.definition.parameters) - ) { - return true; - } - - return false; + return ( + title !== persistedNet.title || + !isSDCPNEqual(petriNet, persistedNet.definition) + ); }, [petriNet, persistedNet, title]); const loadPersistedNet = useCallback( diff --git a/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts b/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts new file mode 100644 index 00000000000..2947827008d --- /dev/null +++ b/libs/@hashintel/petrinaut/src/lib/deep-equal.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from "vitest"; + +import type { SDCPN } from "../core/types/sdcpn"; +import { isSDCPNEqual } from "./deep-equal"; + +const emptyNet: SDCPN = { + places: [], + transitions: [], + types: [], + differentialEquations: [], + parameters: [], +}; + +const sampleNet: SDCPN = { + places: [ + { + id: "p1", + name: "Place 1", + colorId: "c1", + dynamicsEnabled: false, + differentialEquationId: null, + x: 100, + y: 200, + }, + ], + transitions: [ + { + id: "t1", + name: "Transition 1", + inputArcs: [{ placeId: "p1", weight: 1 }], + outputArcs: [{ placeId: "p1", weight: 2 }], + lambdaType: "predicate", + lambdaCode: "return true;", + transitionKernelCode: "return input;", + x: 300, + y: 200, + }, + ], + types: [ + { + id: "c1", + name: "Token", + iconSlug: "circle", + displayColor: "#FF0000", + elements: [{ elementId: "e1", name: "value", type: "real" }], + }, + ], + differentialEquations: [], + parameters: [ + { + id: "param1", + name: "Rate", + variableName: "rate", + type: "real", + defaultValue: "1.0", + }, + ], +}; + +describe("isSDCPNEqual", () => { + it("returns true for two empty nets", () => { + expect(isSDCPNEqual(emptyNet, { ...emptyNet })).toBe(true); + }); + + it("returns true for identical nets", () => { + const copy = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + expect(isSDCPNEqual(sampleNet, copy)).toBe(true); + }); + + it("returns true for same reference", () => { + expect(isSDCPNEqual(sampleNet, sampleNet)).toBe(true); + }); + + it("returns false when a place differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.places[0]!.name = "Renamed"; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a transition arc weight differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.transitions[0]!.outputArcs[0]!.weight = 99; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a parameter is added", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.parameters.push({ + id: "param2", + name: "Extra", + variableName: "extra", + type: "integer", + defaultValue: "0", + }); + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when a color element differs", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.types[0]!.elements[0]!.type = "boolean"; + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); + + it("returns false when places array has different length", () => { + const modified = JSON.parse(JSON.stringify(sampleNet)) as SDCPN; + modified.places.push({ + id: "p2", + name: "Place 2", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 0, + y: 0, + }); + expect(isSDCPNEqual(sampleNet, modified)).toBe(false); + }); +}); diff --git a/libs/@hashintel/petrinaut/src/lib/deep-equal.ts b/libs/@hashintel/petrinaut/src/lib/deep-equal.ts new file mode 100644 index 00000000000..e6a7e871112 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/lib/deep-equal.ts @@ -0,0 +1,74 @@ +import type { SDCPN } from "../core/types/sdcpn"; + +/** + * Recursively compare two values for structural equality. + * + * Handles primitives, arrays, and plain objects. Does not handle + * special types like Date, RegExp, Map, Set, etc. — those are not + * used in SDCPN definitions. + */ +const deepEqual = (a: unknown, b: unknown): boolean => { + // Same reference or identical primitive + if (a === b) { + return true; + } + + // Different types can never be equal + if (typeof a !== typeof b) { + return false; + } + + // One is null but not the other (both-null is caught by `a === b` above) + if (a === null || b === null) { + return false; + } + + // Compare arrays element-by-element + if (Array.isArray(a)) { + if (!Array.isArray(b) || a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + // Compare plain objects by own properties + if (typeof a === "object") { + const objA = a as Record; + const objB = b as Record; + + const propsA = Object.getOwnPropertyNames(objA); + const propsB = Object.getOwnPropertyNames(objB); + + // Different number of properties means not equal + if (propsA.length !== propsB.length) { + return false; + } + + // Every property in `a` must exist in `b` with the same value + for (const prop of propsA) { + if ( + !Object.prototype.hasOwnProperty.call(objB, prop) || + !deepEqual(objA[prop], objB[prop]) + ) { + return false; + } + } + + return true; + } + + return false; +}; + +/** + * Check if two SDCPN definitions are structurally identical. + * + * Performs a recursive deep comparison of all fields, so that + * additions to the SDCPN type are automatically covered. + */ +export const isSDCPNEqual = (a: SDCPN, b: SDCPN): boolean => deepEqual(a, b); diff --git a/libs/@hashintel/petrinaut/src/petrinaut.tsx b/libs/@hashintel/petrinaut/src/petrinaut.tsx index f054df4ae0d..c56d600da35 100644 --- a/libs/@hashintel/petrinaut/src/petrinaut.tsx +++ b/libs/@hashintel/petrinaut/src/petrinaut.tsx @@ -20,6 +20,8 @@ import { EditorProvider } from "./state/editor-provider"; import { SDCPNProvider } from "./state/sdcpn-provider"; import { EditorView } from "./views/Editor/editor-view"; +export { isSDCPNEqual } from "./lib/deep-equal"; + export type { Color, DifferentialEquation, From 4cc9776f5e6d40d3f668cd495bb2ef14dec1b03b Mon Sep 17 00:00:00 2001 From: Chris Feijoo Date: Fri, 20 Feb 2026 15:41:25 +0100 Subject: [PATCH 18/18] Add @todos to CSP changes --- apps/hash-frontend/src/lib/csp.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/hash-frontend/src/lib/csp.ts b/apps/hash-frontend/src/lib/csp.ts index f264045eb8f..171adfb0a5b 100644 --- a/apps/hash-frontend/src/lib/csp.ts +++ b/apps/hash-frontend/src/lib/csp.ts @@ -24,6 +24,7 @@ export const buildCspHeader = (nonce: string): string => { "https://apis.google.com", // Vercel toolbar / live preview widget "https://vercel.live", + // @todo FE-488 will make this unnecessary // Monaco Editor loaded from CDN by @monaco-editor/react (used by petrinaut) "https://cdn.jsdelivr.net", ], @@ -33,6 +34,7 @@ export const buildCspHeader = (nonce: string): string => { // Required for Emotion/MUI CSS-in-JS inline style injection. // @todo Use nonce-based approach via Emotion's cache `nonce` option. "'unsafe-inline'", + // @todo FE-488 will make this unnecessary // Monaco Editor stylesheet loaded from CDN by @monaco-editor/react (used by petrinaut) "https://cdn.jsdelivr.net", ], @@ -51,6 +53,7 @@ export const buildCspHeader = (nonce: string): string => { "font-src": [ "'self'", + // @todo FE-488 will make this unnecessary // Monaco Editor CSS embeds the Codicon icon font as an inline base64 data URI "data:", ],