Skip to content

Conversation

@kube
Copy link
Collaborator

@kube kube commented Dec 9, 2025

H-5840 (part of H-5684)

🌟 What is the purpose of this PR?

Screenshot 2025-12-15 at 11.04.48.png

  • Add global DiagnosticsPanel with linting
  • First step of having a global SDCPN checker (H-5684)

🔍 What does this change?

  • A new DiagnosticsPanel is present at the bottom of the UI (toggle using the new Diagnostics icon in BottomBar)
  • createSDCPNLanguageService that takes an SDCPN as input and lints
    • This does not handle the changes on SDCPN at the moment, so needs to be re-instantiated every time.
  • checkSDCPN function, that creates

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • In some cases, the linter fails to see an invalid property access.

🐾 Next steps

  • H-5841 Linting of all SDCPN properties, not just code
  • H-5839 create LSP from current solution, and re-use for MonacoEditor instances

🛡 What tests cover this?

  • This PR adds basic tests for createSDCPN function.
    • These will be reworked/enhanced progressively.

❓ How to test this?

Use latest Petrinaut deployment (in thread)

@github-actions github-actions bot added area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Dec 9, 2025
@kube kube changed the title Still PoC H-5684: SDCPN checker and global TypeScript typecheck Dec 9, 2025
@kube kube force-pushed the cf/h-5684-implement-live-checks-on-sdcpn-validity branch from bb162e7 to a1940ac Compare December 9, 2025 11:11
@kube kube force-pushed the cf/h-5684-implement-live-checks-on-sdcpn-validity branch 2 times, most recently from 496cc7c to 79a4ea8 Compare December 15, 2025 01:37
@kube kube changed the title H-5684: SDCPN checker and global TypeScript typecheck H-5840: SDCPN diagnostics panel with global type-checker Dec 15, 2025
@kube kube marked this pull request as ready for review December 15, 2025 01:38
@cursor
Copy link

cursor bot commented Dec 15, 2025

PR Summary

Adds a Diagnostics Panel powered by a TypeScript language service to validate SDCPN code, integrated into the editor UI and simulation flow with tests.

  • UI/Editor:
    • Diagnostics Panel: New bottom panel (DiagnosticsPanel) with resizable UI, grouped errors, and selection linking; toggle via new indicator in BottomBar.
    • State: Introduces CheckerProvider and editor store fields for diagnostics visibility/size; adjusts PropertiesPanel/LeftSideBar layouts to coexist with the panel.
  • Core/Checker:
    • TypeScript LS: Implements virtual-file TS language service (create-sdcpn-language-service, create-language-service-host, file-paths) to type-check differential equations, transition lambdas, and kernels.
    • Validation API: Adds checkSDCPN returning grouped diagnostics; extensive unit tests (checker.test.ts) and helper createSDCPN for test setups.
  • Simulation:
    • Pre-check: simulation-store runs checkSDCPN before initializing; surfaces first TS error in state.
    • Tests: Updates simulation tests to new code shapes and names.
  • Build/Config:
    • Vite: defines minimal process.versions shim for TS LS in browser; adds vite-env.d.ts.
    • Package: ensures typescript dependency; changeset for minor release.

Written by Cursor Bugbot for commit 239cfae. This will update automatically on new commits. Configure here.

getCompilationSettings: () => COMPILER_OPTIONS,
getScriptVersion: () => "0",
getCurrentDirectory: () => "",
getDefaultLibFileName: () => "lib.2015.core.d.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default lib filename does not match the bundled lib files. Returns "lib.2015.core.d.ts" but the BUNDLED_LIBS map uses "lib.es2015.core.d.ts" as the key. This mismatch causes TypeScript to fail loading the default lib file, which means basic JavaScript types (Array, Object, String, etc.) won't be available during type checking.

Fix:

getDefaultLibFileName: () => "lib.es2015.core.d.ts",

Or alternatively, update line 22 in COMPILER_OPTIONS to use "lib.2015.core.d.ts" and adjust the BUNDLED_LIBS map key accordingly.

Suggested change
getDefaultLibFileName: () => "lib.2015.core.d.ts",
getDefaultLibFileName: () => "lib.es2015.core.d.ts",

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

const tokenTuple = Array.from({ length: arc.weight })
.fill(`Color_${sanitizedColorId}`)
.join(", ");
inputTypeProperties.push(` "${place.name}": [${tokenTuple}];`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Duplicate place names generate invalid TypeScript type definitions

The generated TypeScript type definitions use place.name as property keys for the Input and Output types. Since place names are not guaranteed to be unique (the SDCPN type allows multiple places with the same name but different IDs), if a transition has arcs to two places that share the same name, the generated type literal will contain duplicate property names. This produces invalid TypeScript that causes the type-checker to fail with a syntax error when parsing the generated definitions.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fair? If so can we fix?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not, or at least it should not.

We should never have multiple places with the same name, but at the moment we do not do this verification (that should be done in the part of the checker that does not do the type-checking).

In any case, this will not be the responsibility of the type-checker part.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if it results in a linting error, that's actually a good thing, even if it's not by-design.

@graphite-app graphite-app bot requested a review from a team December 15, 2025 11:18
Copy link
Member

@CiaranMn CiaranMn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition! A few minor comments


// Check all differential equations
for (const de of sdcpn.differentialEquations) {
const filePath = `differential_equations/${de.id}/code.ts`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a generateCodeFilePath(id: string, type: ItemType): string function in create-sdcpn-language-service which is then used both here and when creating the files?

At the moment they are magic strings which need keeping in sync manually if they change.

Same applies to other file paths below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes much clearer indeed, I will add that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const tokenTuple = Array.from({ length: arc.weight })
.fill(`Color_${sanitizedColorId}`)
.join(", ");
inputTypeProperties.push(` "${place.name}": [${tokenTuple}];`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fair? If so can we fix?

}),

// Properties panel width
propertiesPanelWidth: 450,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a separate variable for this and diagnosticsPanelHeight to avoid repeating them and them potentially going out of sync?

i.e. somewhere up top in the file const propertiesPanelWidth = 450;

// Position offsets (accounting for sidebar padding/margins)
const MIN_HEIGHT = 100;
const MAX_HEIGHT = 600;
const LEFT_SIDEBAR_WIDTH = 344; // 320px + 24px padding
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we pull the sidebar width from where it's defined? And the padding from wherever that is?

Copy link
Collaborator Author

@kube kube Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to centralize all panel-related stuff together, including some styles, but ended up with bugs on styles.

If that is okay for you I'll add this on top of the PR fixing PandaCSS, or directly in the one that contains the BottomPanel (DiagnosticsPanel is already removed in my new PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay sure – I don't see how just defining some numbers as variables rather than hard-coding them several times would cause style bugs, so hopefully easy enough to fix

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about that, but possibly one of the issues I had was:

  • When the constant is in an external file, PandaCSS cannot statically analyze/create the style by just reading one file.

So this LEFT_SIDEBAR_WIDTH would either:

  • Not be in the css() anymore, but in an inline style
  • Replaced by a CSS variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But given PandaCSS was not configured properly, I was not sure of what was causing the issue exactly.

import { TransitionProperties } from "./transition-properties";
import { TypeProperties } from "./type-properties";

const startingWidth = 450;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have 450 again, definitely this could go out of sync with the 450s in the editor store, should all draw from the same variable

@kube kube force-pushed the cf/h-5684-implement-live-checks-on-sdcpn-validity branch from 4188954 to da1fbd9 Compare December 17, 2025 18:57
*/
function sanitizeColorId(colorId: string): string {
return colorId.replace(/[^a-zA-Z0-9_]/g, "");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Color ID sanitization causes type name collisions

The sanitizeColorId function removes non-alphanumeric characters to create valid TypeScript identifiers, but this can cause type name collisions. If two colors have IDs that differ only in special characters (e.g., "color-1" and "color_1" both sanitize to "color1"), they'll generate types with identical names (Color_color1) in different files. When both types are imported in the same transition definition file, the second import shadows the first, causing incorrect type checking where places reference the wrong color's type structure.

Additional Locations (1)

Fix in Cursor Fix in Web

useEffect(() => {
const result = checkSDCPN(petriNetDefinition);
setCheckResult(result);
}, [petriNetDefinition]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Redundant SDCPN check on initial component mount

The checkSDCPN function is called twice on initial mount. The useState initializer (line 22-24) calls it to set the initial state, and then the useEffect (lines 27-30) immediately runs after mount and calls it again because petriNetDefinition is in the dependency array. Since checkSDCPN creates a TypeScript language service internally via ts.createLanguageService, this double execution is expensive and causes unnecessary performance overhead on every component mount.

Fix in Cursor Fix in Web

useEffect(() => {
const result = checkSDCPN(petriNetDefinition);
setCheckResult(result);
}, [petriNetDefinition]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Expensive type-checker runs twice on initial mount

The checkSDCPN function runs twice on initial mount — once in the useState initializer (line 22-24) and again immediately in the useEffect (line 27-30) since petriNetDefinition is in its dependency array. Since checkSDCPN creates a TypeScript language service and runs full diagnostic checks, this is an expensive operation to duplicate. Consider either using only the useState initializer with a ref to skip the first useEffect run, or initializing state as null and only using the effect.

Fix in Cursor Fix in Web

position: "fixed",
bottom: PANEL_MARGIN,
left: leftOffset,
right: PANEL_MARGIN,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: DiagnosticsPanel overlaps PropertiesPanel due to unused width

The propertiesPanelWidth is stored in the editor state with a comment indicating it's "for DiagnosticsPanel positioning", and PropertiesPanel syncs its width to this value. However, DiagnosticsPanel never reads this value — it uses a fixed right: PANEL_MARGIN (12px) instead. When both panels are open, the DiagnosticsPanel extends underneath the PropertiesPanel (which has higher z-index), making a large portion of the diagnostics content visually obscured and potentially inaccessible.

Additional Locations (1)

Fix in Cursor Fix in Web

@github-actions github-actions bot added the area/infra Relates to version control, CI, CD or IaC (area) label Dec 19, 2025
useEffect(() => {
const result = checkSDCPN(petriNetDefinition);
setCheckResult(result);
}, [petriNetDefinition]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Type checker runs twice on component mount

The checkSDCPN function is called twice on initial mount. It's first invoked in the useState lazy initializer (line 22-24), then immediately again in the useEffect (line 27-30) which runs on mount because petriNetDefinition is in its dependency array. Since checkSDCPN creates a new TypeScript language service on each call (an expensive operation involving virtual file generation and ts.createLanguageService), this causes redundant computation and noticeable delay on initial load. The useEffect should skip its first run or the useState initializer should be removed.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be fixed in H-5839

@graphite-app graphite-app bot requested a review from a team December 19, 2025 10:21
@kube kube requested a review from CiaranMn December 19, 2025 11:11
@kube kube added this pull request to the merge queue Dec 19, 2025
Merged via the queue into main with commit bc9456d Dec 19, 2025
44 checks passed
@kube kube deleted the cf/h-5684-implement-live-checks-on-sdcpn-validity branch December 19, 2025 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants