Skip to content

H-5839, FE-488: Petrinaut: LSP-like interface for Diagnostics and Code Editor#8300

Open
kube wants to merge 22 commits intomainfrom
cf/h-5839-create-an-lsp-and-re-use-in-monaco-editor
Open

H-5839, FE-488: Petrinaut: LSP-like interface for Diagnostics and Code Editor#8300
kube wants to merge 22 commits intomainfrom
cf/h-5839-create-an-lsp-and-re-use-in-monaco-editor

Conversation

@kube
Copy link
Collaborator

@kube kube commented Jan 23, 2026

🌟 What is the purpose of this PR?

Add a TypeScript Language Service layer for Petrinaut's Monaco editors, providing real-time diagnostics, completions, hover info, and signature help — all running off the main thread in a WebWorker, communicating via an LSP-inspired JSON-RPC 2.0 protocol with standard vscode-languageserver-types.

graph TD
  subgraph Main Thread
    SDCPN["SDCPNContext"] --updates--> LC["LanguageClientContext"]
    LC --> Sync["Sync Components"]
    Monaco["MonacoContext"] --> Sync
    CodeEditor --uses--> Monaco
  end

  subgraph WebWorker
    Worker["language-server.worker"] --> LS["SDCPNLanguageServer"]
  end

  LC -- "notifications" --> Worker
  Worker -. "diagnostics" .-> LC
  Sync -- "requests" --> Worker
  Worker -. "responses" .-> Sync
Loading

Protocol messages (JSON-RPC 2.0)

Direction Method Payload
Main → Worker initialize, sdcpn/didChange, textDocument/didChange SDCPN model / document text
Worker → Main textDocument/publishDiagnostics Diagnostic[] per URI
Main → Worker textDocument/completion PositionCompletionList
Main → Worker textDocument/hover PositionHover
Main → Worker textDocument/signatureHelp PositionSignatureHelp

Note: While the SDCPNLanguageServer is instantiated once and reused, every change — whether to the net structure, types, or code — currently sends the full SDCPN to the WebWorker via sdcpn/didChange. This will need to be made more granular in the future (e.g. per-item deltas).

🔗 Related links

🔍 What does this change?

Architecture

  • Extract Monaco setup into a dedicated async-loaded module (monaco/provider.tsx) with a module-level lazy singleton to prevent re-initialization
  • Consolidate the LSP layer into a self-contained module (lsp/)
  • Move the TypeScript language server to a WebWorker (lsp/worker/language-server.worker.ts) with a JSON-RPC 2.0 protocol
  • Bundle TypeScript lib files into the worker (no CDN dependency)

LSP-inspired protocol (lsp/worker/protocol.ts)

  • All data types use official LSP types from vscode-languageserver-types (Diagnostic, CompletionItem, CompletionList, Hover, SignatureHelp, Position, Range)
  • Notifications: initialize, sdcpn/didChange, textDocument/didChange
  • Requests: textDocument/completion, textDocument/hover, textDocument/signatureHelp
  • Server push: textDocument/publishDiagnostics (per-URI diagnostic notifications)
  • Position model uses LSP Position { line, character } with offset↔position conversion utilities bridging the TS LanguageService

Persistent LanguageService (lsp/lib/create-sdcpn-language-service.ts)

  • SDCPNLanguageServer class creates ts.createLanguageService() once and reuses it across SDCPN changes
  • syncFiles(sdcpn) diffs virtual files incrementally (add/remove/update) rather than recreating everything
  • updateDocumentContent() for single-file typing updates
  • getFileContent() for offset↔position conversion

Shared modules

  • lsp/lib/document-uris.ts: Single source of truth for URI ↔ file path ↔ item type mapping, consumed by both the worker and Monaco components
  • lsp/lib/ts-to-lsp.ts: Pure TS → LSP type conversion functions (toLspSeverity, toCompletionItemKind, serializeDiagnostic), extracted from the worker for testability
  • lsp/lib/position-utils.ts: Offset ↔ LSP Position conversion utilities

Language features (Monaco sync components)

  • Diagnostics: DiagnosticsSync maps LSP Diagnostic ranges to Monaco markers (diagnostics-sync.tsx)
  • Completions: CompletionSync with LSP CompletionItemKind → Monaco kind mapping (completion-sync.tsx)
  • Hover: HoverSync rendering LSP MarkupContent (hover-sync.tsx)
  • Signature help: SignatureHelpSync with LSP SignatureHelp → Monaco mapping (signature-help-sync.tsx)
  • All sync components convert between Monaco 1-based and LSP 0-based positions at the boundary

Cleanup

  • Remove @dnd-kit dependency and feature flags
  • Remove rollup-plugin-visualizer dependency

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 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

🛡 What tests cover this?

  • checker.test.ts: 20 tests covering SDCPN validation with the SDCPNLanguageServer
  • create-sdcpn-language-service.test.ts: 11 tests covering dot completions, top-level completions, updateDocumentContent mutability, syncFiles structural updates, and differential equation completions
  • position-utils.test.ts: 12 tests covering offsetToPosition, positionToOffset, and roundtrip identity
  • document-uris.test.ts: 22 tests covering URI construction, parsing, file path conversion, and roundtrip identity for all item types
  • ts-to-lsp.test.ts: 38 tests covering TS diagnostic severity → LSP severity mapping, all TS ScriptElementKind → LSP CompletionItemKind mappings, and full serializeDiagnostic conversion (multi-line ranges, chained messages, defaults)

🐾 Next steps

  • Adopt a real LSP library: progressively migrate to an established LSP client/server library (e.g. vscode-languageserver / vscode-languageclient) to replace custom JSON-RPC mapping and reduce protocol boilerplate.
  • Visualizer code support: re-add completions and diagnostics for Place visualizer editors — this is separate from the SDCPN verification/checker scope and was intentionally left out of this initial implementation.

@vercel
Copy link

vercel bot commented Jan 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Error Error Feb 26, 2026 11:31pm
petrinaut Ready Ready Preview Feb 26, 2026 11:31pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign Ignored Ignored Preview Feb 26, 2026 11:31pm
hashdotdesign-tokens Ignored Ignored Preview Feb 26, 2026 11:31pm

@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 Jan 23, 2026
Copy link
Collaborator Author

kube commented Jan 23, 2026

@kube kube changed the base branch from main to graphite-base/8300 February 19, 2026 10:09
@kube kube force-pushed the cf/h-5839-create-an-lsp-and-re-use-in-monaco-editor branch from 18ec8e9 to 90fb012 Compare February 19, 2026 10:09
@kube kube changed the base branch from graphite-base/8300 to cf/h-5780-remove-petrinaut-old-package-from-hash-monorepo February 19, 2026 10:09
@github-actions github-actions bot added area/deps Relates to third-party dependencies (area) area/apps > hash* Affects HASH (a `hash-*` app) area/apps labels Feb 19, 2026
@kube kube changed the title H-5839: Add LSP layer for Petrinaut Monaco editors H-5839, FE-488: Add LSP layer for Petrinaut Monaco editors Feb 19, 2026
@kube kube force-pushed the cf/h-5839-create-an-lsp-and-re-use-in-monaco-editor branch from 213628d to 5cb42ad Compare February 20, 2026 01:47
@kube kube force-pushed the cf/h-5780-remove-petrinaut-old-package-from-hash-monorepo branch from 7c811a0 to 16b35f4 Compare February 20, 2026 01:47
kube and others added 13 commits February 26, 2026 23:25
…o editors

Wire TypeScript LanguageService features through the checker WebWorker
to Monaco editors via JSON-RPC. The LanguageServiceHost is now mutable
(per-file version tracking) so completions reflect current editor state.

- Completions: registerCompletionItemProvider with ScriptElementKind mapping
- Hover: registerHoverProvider backed by getQuickInfoAtPosition
- Signature help: registerSignatureHelpProvider backed by getSignatureHelpItems
- All three adjust offsets to account for injected declaration prefixes
- Add tests for completions and updateFileContent in the language service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…DCPN changes

Convert the checker communication layer from ad-hoc method names and types to an
LSP-inspired protocol (initialize, sdcpn/didChange, textDocument/didChange,
textDocument/completion, textDocument/hover, textDocument/signatureHelp), with
push-based diagnostics via textDocument/publishDiagnostics notifications.

Replace the factory function with a persistent SDCPNLanguageServer class that
creates ts.createLanguageService() once and incrementally syncs virtual files
(add/remove/update) when the SDCPN structure changes, instead of recreating
from scratch on every mutation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ypes

Replace hand-rolled Diagnostic, CompletionItem, Hover, and SignatureHelp types
with official LSP types from vscode-languageserver-types. Protocol now uses
LSP Position (line/character) instead of raw offsets, with offset<->position
conversion utilities bridging the TS LanguageService (offset-based) and LSP
(Position-based) worlds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use React 19 Context shorthand, derive totalDiagnosticsCount directly
instead of useMemo, and extract pure helper functions for building the
diagnostics map.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix import sorting, remove unused imports, simplify unnecessary type
assertions and conditions, avoid array index keys, and fix no-return-assign
in provider singleton.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
….worker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntent

The worker was converting Monaco positions against the full file content
(prefix + user code), but Monaco positions are relative to the visible
user code only. This caused double-counting of the prefix offset since
SDCPNLanguageServer methods already add prefixLength internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add key={petriNetId} so the language client remounts with fresh state
when switching between Petri nets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Monaco providers now call notifyDocumentChanged before each
completion/hover/signatureHelp request so the worker always has
up-to-date content when converting positions. Without this, typing
a trigger character (e.g. ".") could race the SDCPN state sync,
causing the worker to compute offsets against stale text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cursor
Copy link

cursor bot commented Feb 26, 2026

PR Summary

Medium Risk
Moderate risk because it replaces Petrinaut’s in-browser TypeScript tooling and diagnostics flow with a new WebWorker + JSON-RPC layer and updates build/CSP settings that could affect editor/runtime behavior in production.

Overview
Introduces a new LSP-inspired language service layer for Petrinaut’s Monaco editors, running TypeScript analysis in a WebWorker and exposing diagnostics, completions, hover, and signature help via a small JSON-RPC protocol (lsp/worker/*).

Replaces the previous on-main-thread checker/Monaco typings setup with LanguageClientProvider + MonacoProvider and Monaco sync components that push diagnostics into Monaco markers and forward completion/hover/signature requests to the worker; editor model URIs are centralized via document-uris helpers.

Cleans up and adjusts packaging/security: removes CDN-related CSP allowances, drops the frontend webpack browser fallbacks previously used for typescript, adds vscode-languageserver-types, and tweaks Petrinaut’s Vite worker build replacements to avoid Node/process detection issues in the worker. Also removes arc reordering/feature-flag DnD UI, simplifying arc list items.

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

@graphite-app graphite-app bot requested review from a team February 26, 2026 22:56
@augmentcode
Copy link

augmentcode bot commented Feb 26, 2026

🤖 Augment PR Summary

Summary: This PR introduces an LSP-inspired TypeScript Language Service layer for Petrinaut’s Monaco editors, moving diagnostics and IntelliSense work off the main thread into a WebWorker.

Changes:

  • Adds a JSON-RPC 2.0 protocol (based on vscode-languageserver-types) between main thread and worker.
  • Implements a persistent SDCPNLanguageServer with an incrementally updated virtual FS/LanguageServiceHost.
  • Creates Monaco “sync” components for diagnostics, completion, hover, and signature help backed by the worker.
  • Centralizes URI↔file-path mapping and TS→LSP conversions with dedicated tests.
  • Refactors Petrinaut to use LanguageClientProvider + MonacoProvider and removes the old in-main-thread checker/Monaco typings hook.
  • Updates CSP/Next/Vite configuration to support local Monaco + worker-based TypeScript without CDN dependencies.

Technical Notes: Monaco’s built-in TypeScript worker features are disabled and replaced by worker-backed providers; diagnostics are pushed from the worker and mirrored into Monaco markers.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

} else {
client.notifySDCPNChanged(petriNetDefinition);
}
}, [petriNetDefinition, client]);
Copy link

Choose a reason for hiding this comment

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

useLanguageClient() returns a fresh object each render, so including client in these effect deps can retrigger initialize/notifySDCPNChanged even when petriNetDefinition didn’t change (e.g., after diagnostics state updates). Consider stabilizing client (memoizing the returned API) or depending on specific stable callbacks instead.

Severity: medium

Other Locations
  • libs/@hashintel/petrinaut/src/lsp/provider.tsx:54

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same as above — React Compiler handles this automatically.

/>
</div>
<CodeEditor
path={`inmemory://sdcpn/places/${place.id}/visualizer.tsx`}
Copy link

Choose a reason for hiding this comment

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

This editor uses an inmemory://sdcpn/places/... URI, but the worker URI mapping currently only recognizes transitions + differential equations; with Monaco’s built-in TS features disabled, this model will likely have no completions/hover/diagnostics. Is the visualizer intentionally excluded, or should it be added to the URI mapping/worker support?

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Intentional — see above.

i++;
}

return i + position.character;
Copy link

Choose a reason for hiding this comment

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

positionToOffset can return an offset beyond text.length when position.line/position.character is out of range, which could propagate into TS LanguageService queries. Consider clamping to the text length (similar to offsetToPosition’s clamping) for defensive safety.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Valid — adding a clamp for symmetry with offsetToPosition.

…offsetToPosition`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps area/deps Relates to third-party dependencies (area) 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.

2 participants