Skip to content

feat(vscode): Tier 1 live diagram panel over new opensysml/render LSP requests - #330

Open
devin-ai-integration[bot] wants to merge 7 commits into
mainfrom
devin/1787174647-vscode-tier1-diagram
Open

feat(vscode): Tier 1 live diagram panel over new opensysml/render LSP requests#330
devin-ai-integration[bot] wants to merge 7 commits into
mainfrom
devin/1787174647-vscode-tier1-diagram

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 19, 2026

Copy link
Copy Markdown

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/Edge gain an Origin{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). Span is the whole declaration — so a cursor anywhere inside it is inside the element — and Name the declared identifier alone, which is where a client navigates. Text/Mermaid/Markdown are untouched, so every existing golden in internal/core/view/testdata and internal/repl is unchanged. Rendering.Data() flattens the containment tree into a protocol-free view.Data (nodes with Parent IDs, edges, rows + row origins, notices) — the LSP layer marshals it.

Two prerequisites fell out of wanting real origins:

  • symbols.SetDocName iterated scope.Members(), which skips anonymous members, so connect a to b; had an empty DocName and could not be located. It now walks AllMembers(). This also fixes solver comments for anonymous constraints — hence the four internal/core/solve/testdata/*.smt2 goldens now reading — analysis MassBudget, at objectives.sysml:15:4 instead of dropping the location. That is a fix, not a regression.
  • Lowering kept no back-pointer to what produced an edge: lower.Transition gains Decl ast.Node and ActionGraph gains Successions 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 — new render.go with RenderView(doc, fqn) and Views(doc) (name, kind, supported, reason for sequence/geometry/textual), built on newResolver + semantics.NewModel + view.SourceText exactly as Session.viewRenderer does. 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 no view declaration still renders and says so via Stated. Naming no view renders the sole view, or reports the ambiguity.

internal/lsp/render.goopensysml/render and opensysml/views requests plus the opensysml/renderChanged notification, dispatched ahead of the generic protocol handler, and experimental: { openSysmlRender: true } in the initialize result. Renders carry the document version they were made from. An origin is marshalled as {uri, range, selectionRange}, mirroring LocationLink. renderChanged is queued after the analysis that publishes diagnostics and debounced on the existing crossDocRefreshWindow, carrying only URI + version — push-notify, pull-artifact.

editors/vscodeSysML: Open Diagram opens one webview per document beside the editor with a opensysml/views-fed picker. The command is registered only when the server advertised the capability, so an older sysml-lsp degrades 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 with securityLevel: "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 in vscode.setState(), so a panel torn down while hidden comes back drawn rather than empty. The client asks for no form, so the server writes the machine form of the kind it rendered: Mermaid for a diagram, Markdown for a table (shown as text). Node clicks showTextDocument at the origin's selectionRange, and onDidChangeTextEditorSelection highlights 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), so bareID() 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

$ go build ./...
$ go vet ./...
$ gofmt -l .          # empty
$ go test ./...       # all packages ok
$ staticcheck ./...   # clean
$ gosec -quiet ./...  # 8 issues, all pre-existing (api/proto/sysml.pb.go, cmd/sysml/main.go, internal/core/rdf, internal/core/runtime)
$ cd editors/vscode && npm run typecheck && npm run build
$ make build && make vscode-package

npm run typecheck now checks both TypeScript projects (Node extension and DOM webview).

Nothing in docs/project/spec-compliance.md changes: 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 in docs/guide/08-editors.md, and editors/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 on examples/views-demo.sysml, live redraw while typing, clicking a node and landing on its declaration, cursor-driven highlighting, the #table pseudo-view, hidden-panel restore, and a mid-keystroke parse error leaving the last diagram dimmed.

Clicking state hover in the state diagram selects its declaration, and the node under the cursor is outlined:

state diagram click-to-source and highlight

A render that fails mid-edit keeps the previous diagram, dimmed, with the error in the status line:

stale diagram after a failed render

Known limitation found while testing: in a workspace that is this repository, the parser fixture internal/core/parser/testdata/parse/view_expose.sysml declares a package Views that shadows the standard library's, so a view stating render asElementTable there 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 only examples/views-demo.sysml the same view renders as Markdown.

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/45017113e7a6461bb6dd5e55a78e94e7
Requested by: @HuiJun

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>
@HuiJun HuiJun self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author
Original prompt from Devin Bot

Implement Tier 1 ("live diagram panel") of the visual-modeling design for the OpenSysML VS Code extension, in repo JPL-Devin/OpenSysML.

READ THE DESIGN FIRST. The design note is not on main yet — it is on PR #329, branch devin/1787174324-vscode-visual-modeling-design, at docs/internals/design/vscode-visual-modeling.md. Fetch that branch and read the whole file, plus AGENTS.md (its golden rules are binding: correctness over expedience, no stubs, no hacks, complete features, and the four-layer test contracts). Branch your work off main, then git checkout devin/1787174347... is NOT needed — just read the doc via git show origin/devin/1787174324-vscode-visual-modeling-design:docs/internals/design/vscode-visual-modeling.md. Implement ONLY Tier 1. Do not implement Tier 2 (opensysml/applyModelEdit, new internal/core/edit operations) or Tier 3 (layout sidecar, SVG canvas) — those are separate, later work.

Scope, as the design specifies:

  1. Go — internal/core/view

    • Give view.Node and view.Edge a source origin (the document name plus the source.Span the element was declared at), populated wherever a node/edge is built from a symbol or a lowered graph element. It must be additive: Text, Mermaid and Markdown output must be byte-identical to today, so the existing goldens in internal/core/view/testdata and internal/repl must not move. An element with no locatable declaration (e.g. a cached standard-library symbol) carries no origin rather than a bogus one.
    • Add a plain data type in view for the machine-consumable shape of a Rendering (nodes, edges, rows, notices, origins). Keep protocol/JSON concerns out of view itself — the LSP layer marshals it.
  2. Go — internal/core/model

    • Add workspace-level entry points: RenderView(doc, fqn string) (*view.Rendering, error) and a Views(doc string) listing the document's views with, per view, its rendering kind, whether that kind is suppor... (4113 chars truncated...)

@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 2 commits August 19, 2026 22:20
… 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>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Devin Review found 5 potential issues.

Open in Devin Review

Comment thread editors/vscode/src/diagram.ts
Comment thread editors/vscode/src/diagram.ts
Comment thread editors/vscode/src/webview/tsconfig.json
Comment thread editors/vscode/package.json
Comment on lines +88 to +92
if (result.form === "mermaid") {
const id = `opensysml-diagram-${++drawn}`;
const { svg } = await mermaid.render(id, result.artifact);
diagram.innerHTML = svg;
markNodes(result);

@devin-ai-integration devin-ai-integration Bot Aug 19, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟨 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits August 19, 2026 23:17
Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…ent's members

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
devin-ai-integration[bot]

This comment was marked as resolved.

…r race

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant