feat(vscode): Tier 1 live diagram panel over new opensysml/render LSP requests - #330
feat(vscode): Tier 1 live diagram panel over new opensysml/render LSP requests#330devin-ai-integration[bot] wants to merge 7 commits into
Conversation
Adds source origins to view nodes/edges, workspace RenderView/Views with pseudo-views, the opensysml/render, opensysml/views requests and the opensysml/renderChanged notification, and a Mermaid-bundled VS Code webview panel with click-to-source and cursor highlighting. Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Original prompt from Devin Bot
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
… hidden panels Mermaid 11 ids are decorated on both sides (opensysml-diagram-3-flowchart-n2-0), so click-to-source and cursor highlight never matched a node; the highlight also lost to Mermaid's SVG-injected stylesheet. The panel now asks for no particular form, showing a Markdown table as itself, caches the last rendering in webview state so a hidden panel does not come back blank, and lists reference/lsp.md in the docs nav. Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…ration Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
| if (result.form === "mermaid") { | ||
| const id = `opensysml-diagram-${++drawn}`; | ||
| const { svg } = await mermaid.render(id, result.artifact); | ||
| diagram.innerHTML = svg; | ||
| markNodes(result); |
There was a problem hiding this comment.
🟨 Server-supplied Mermaid artifact is injected into the webview via innerHTML
The webview assigns the SVG string produced from the server's rendering artifact straight into the DOM (diagram.innerHTML = svg in editors/vscode/src/webview/diagram.ts:88-92). The artifact is built from model text (element names, guards, trigger expressions taken verbatim from the document via view.SourceText), so hostile content in a .sysml file flows into the panel's DOM. Mermaid is initialized with securityLevel: "strict" (editors/vscode/src/webview/diagram.ts:31), which sanitizes labels and blocks click handlers, and the panel's CSP allows scripts only by nonce (editors/vscode/src/diagram.ts:381-387), so inline <script>/event handlers injected this way would not execute. The residual risk is a Mermaid sanitizer bypass being enough to inject markup into a privileged webview.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Agreed on the analysis, and no change: Mermaid's own render() output has to reach the DOM as markup, which is why the panel runs Mermaid at securityLevel: "strict" under a nonce CSP with no unsafe-inline. The residual risk is a Mermaid sanitizer bypass, which is the dependency's threat model rather than something this layer can narrow further. Tier 3 replaces this with a rendering-driven SVG canvas that never injects third-party markup.
…p a pick Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…ent's members Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…r race Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Summary
Implements Tier 1 ("live diagram panel") of the visual-modeling design in PR #329: a read-only VS Code diagram panel driven by the same semantic model diagnostics already use. No Tier 2 (
opensysml/applyModelEdit) or Tier 3 (layout sidecar, SVG canvas) work.internal/core/view— provenance, additively.Node/Edgegain anOrigin{Doc string, Span source.Span, Name source.Span}, populated from the symbol, connector, flow, transition or succession the element was built from, and left zero for elements with no locatable declaration (cached stdlib symbols).Spanis the whole declaration — so a cursor anywhere inside it is inside the element — andNamethe declared identifier alone, which is where a client navigates.Text/Mermaid/Markdownare untouched, so every existing golden ininternal/core/view/testdataandinternal/replis unchanged.Rendering.Data()flattens the containment tree into a protocol-freeview.Data(nodes withParentIDs, edges, rows + row origins, notices) — the LSP layer marshals it.Two prerequisites fell out of wanting real origins:
symbols.SetDocNameiteratedscope.Members(), which skips anonymous members, soconnect a to b;had an emptyDocNameand could not be located. It now walksAllMembers(). This also fixes solver comments for anonymous constraints — hence the fourinternal/core/solve/testdata/*.smt2goldens now reading— analysis MassBudget, at objectives.sysml:15:4instead of dropping the location. That is a fix, not a regression.lower.TransitiongainsDecl ast.NodeandActionGraphgainsSuccessions map[ast.Node]map[ast.Node]ast.Node, so behavior edges can carry origins instead of the renderer re-deriving them from the AST.internal/core/model— newrender.gowithRenderView(doc, fqn)andViews(doc)(name, kind, supported, reason forsequence/geometry/textual), built onnewResolver+semantics.NewModel+view.SourceTextexactly asSession.viewRendererdoes. Pseudo-views (#tree,#state:<fqn>,#action:<fqn>,#interconnection:<fqn>, plus#table) pass the exposed set straight to the renderer — nothing is synthesized into the model or the symbol index — so a document with noviewdeclaration still renders and says so viaStated. Naming no view renders the sole view, or reports the ambiguity.internal/lsp/render.go—opensysml/renderandopensysml/viewsrequests plus theopensysml/renderChangednotification, dispatched ahead of the generic protocol handler, andexperimental: { openSysmlRender: true }in the initialize result. Renders carry the documentversionthey were made from. An origin is marshalled as{uri, range, selectionRange}, mirroringLocationLink.renderChangedis queued after the analysis that publishes diagnostics and debounced on the existingcrossDocRefreshWindow, carrying only URI + version — push-notify, pull-artifact.editors/vscode—SysML: Open Diagramopens one webview per document beside the editor with aopensysml/views-fed picker. The command is registered only when the server advertised the capability, so an oldersysml-lspdegrades to today's behavior. Mermaid 11.16.1 is bundled locally by a second esbuild browser bundle (dist/webview.js); the webview runs under a strict nonce CSP withsecurityLevel: "strict", no CDN. Renders are requested only while visible; a failed render keeps the last good diagram on screen dimmed with the error in the status line, never blanking the panel — the webview caches the last result invscode.setState(), so a panel torn down while hidden comes back drawn rather than empty. The client asks for noform, so the server writes the machine form of the kind it rendered: Mermaid for a diagram, Markdown for a table (shown as text). Node clicksshowTextDocumentat the origin'sselectionRange, andonDidChangeTextEditorSelectionhighlights the node whose declaration range contains the cursor.Two things GUI testing found and this PR fixes rather than works around: Mermaid 11 decorates node ids on both sides (
opensysml-diagram-3-flowchart-n2-0), sobareID()strips the marker wherever it appears rather than only at the front; and Mermaid injects an SVG<style>block that beat the highlight rule, so the selected node's stroke is set!important.Verification
npm run typechecknow checks both TypeScript projects (Node extension and DOM webview).Nothing in
docs/project/spec-compliance.mdchanges: Tier 1 renders, it does not change semantics. New docs:docs/reference/lsp.md(custom requests, capability negotiation, pseudo-views, origins), a diagram-panel section indocs/guide/08-editors.md, andeditors/vscode/README.md.GUI evidence
Driven end to end in desktop VS Code with the packaged VSIX per
.agents/skills/testing-vscode-extension/SKILL.md: server discovery, opening the panel onexamples/views-demo.sysml, live redraw while typing, clicking a node and landing on its declaration, cursor-driven highlighting, the#tablepseudo-view, hidden-panel restore, and a mid-keystroke parse error leaving the last diagram dimmed.Clicking
state hoverin the state diagram selects its declaration, and the node under the cursor is outlined:A render that fails mid-edit keeps the previous diagram, dimmed, with the error in the status line:
Known limitation found while testing: in a workspace that is this repository, the parser fixture
internal/core/parser/testdata/parse/view_expose.sysmldeclares apackage Viewsthat shadows the standard library's, so a view statingrender asElementTablethere does not resolve to a standard rendering and lists as unsupported. That is pre-existing workspace name resolution, not the render layer; from a workspace holding onlyexamples/views-demo.sysmlthe same view renders as Markdown.Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/45017113e7a6461bb6dd5e55a78e94e7
Requested by: @HuiJun