Skip to content

Commit 50ea382

Browse files
committed
docs: add vue editor and lsp support design
1 parent aa43a82 commit 50ea382

1 file changed

Lines changed: 372 additions & 0 deletions

File tree

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# Vue Editor and LSP Support Design
2+
3+
> Status: Draft
4+
> Date: 2026-05-25
5+
> Scope: `packages/web/src/features/code-editor/*`, `packages/server/src/lsp/*`, `packages/server/src/lsp-tools/*`, `packages/server/src/commands/lsp.ts`, `packages/core/src/domain/lsp.ts`, related tests, package dependencies for Vue language tooling
6+
7+
## Goal
8+
9+
Add first-class Vue Single File Component support to the Monaco-based editor so `.vue` files behave like supported code files instead of plain text.
10+
11+
The feature should deliver:
12+
13+
- Vue syntax highlighting for `.vue` files
14+
- workspace-backed Vue editor models using a stable Monaco language id
15+
- Vue LSP session startup through the existing lazy `auto` runtime flow
16+
- Vue diagnostics, hover, go-to-definition, references, and document symbols through Volar
17+
- managed installation and recovery UX consistent with the existing Python, Go, and Rust LSP flow
18+
19+
## Problem
20+
21+
The current editor pipeline recognizes TypeScript, JavaScript, Python, Go, Rust, and a few markup styles by extension. `.vue` is not mapped in the editor language detector, so it falls back to `plaintext`.
22+
23+
That creates two failures:
24+
25+
1. the Monaco editor does not provide meaningful Vue syntax highlighting
26+
2. the LSP pipeline does not consider `.vue` eligible for session startup, so no language intelligence is available
27+
28+
For Vue projects, this means:
29+
30+
- `.vue` files are visually degraded
31+
- template and `<script setup>` content are not understood semantically
32+
- cross-file navigation into Vue components is unavailable
33+
- no Vue-aware diagnostics surface in the editor
34+
35+
## Decision
36+
37+
Adopt a dedicated Vue path through both editor language detection and LSP resolution.
38+
39+
The system should use:
40+
41+
1. a distinct Monaco language id: `vue`
42+
2. a dedicated `LspServerKind`: `vue`
43+
3. the official Vue language server package, `@vue/language-server` (Volar), for semantic features
44+
4. the existing managed-tool and lazy-session architecture instead of a Vue-specific side channel
45+
46+
This is preferred over treating `.vue` as `html` or routing it through the TypeScript LSP because Vue SFC semantics require a server that understands the relationship between template, script, styles, props, emits, and generated TypeScript state.
47+
48+
## Non-Goals
49+
50+
This phase does not include:
51+
52+
- Vue-specific autocomplete beyond whatever Volar already exposes through the existing LSP capability surface
53+
- rename, code actions, formatting, or semantic token customization
54+
- support for non-SFC Vue-related formats such as `.astro` or custom template containers
55+
- a generic third-party language registration system
56+
57+
## Product Semantics
58+
59+
### Keep Editing Non-Blocking
60+
61+
If Vue language tooling is unavailable, the editor must still open and edit `.vue` files normally.
62+
63+
The degraded state should be:
64+
65+
- syntax highlighting if Monaco Vue language registration succeeds
66+
- no hover, definition, references, symbols, or diagnostics until LSP is ready
67+
- a recoverable inline notice if the Vue language server is missing or failed
68+
69+
### Match Existing LSP Runtime Behavior
70+
71+
Vue support should follow the same runtime contract as existing LSP-backed languages:
72+
73+
- default mode remains `auto`
74+
- session startup is lazy and only happens when an eligible workspace-backed `.vue` file is attached
75+
- sessions are keyed by `workspaceId + server kind`
76+
- idle sessions are automatically disposed by the existing TTL logic
77+
78+
No Vue-specific eager startup or background indexing should be added.
79+
80+
## Architecture
81+
82+
Vue support should be delivered in two coordinated layers.
83+
84+
### Layer 1: Monaco Vue Language
85+
86+
The web app must stop treating `.vue` as unknown text. It should map `.vue` to a dedicated Monaco language id, `vue`, and ensure the editor model uses that id for both workspace-backed and standalone files.
87+
88+
The Monaco language implementation should be lightweight and pragmatic:
89+
90+
- register `vue` once during editor bootstrap
91+
- provide a tokenizer suitable for Vue SFC structure
92+
- prioritize useful highlighting for `<template>`, `<script>`, `<script setup>`, `<style>`, directives, interpolation, and HTML-like structure
93+
94+
The initial implementation does not need to invent a large custom language subsystem. It only needs to establish a stable `vue` Monaco language id and a reasonable syntax-highlighting baseline.
95+
96+
### Layer 2: Vue LSP Through Volar
97+
98+
The backend must treat `.vue` as a supported LSP language family with a dedicated `vue` server kind.
99+
100+
The LSP path should follow the same flow as existing languages:
101+
102+
1. the editor attaches a workspace-backed model with `monacoLanguage: "vue"`
103+
2. the frontend bridge resolves `vue` as an LSP-capable language
104+
3. the frontend sends `lsp.ensureSession`
105+
4. the backend resolves the `vue` tool definition
106+
5. the backend launches a workspace-scoped Volar session if the tool is available
107+
6. the frontend opens and syncs the document through the existing LSP bridge
108+
7. Monaco providers surface definition, hover, references, symbols, and diagnostics
109+
110+
## Alternatives Considered
111+
112+
### Option 1: Treat `.vue` as `html` and stop there
113+
114+
Pros:
115+
116+
- smallest implementation effort
117+
- improves visual readability quickly
118+
119+
Cons:
120+
121+
- no real Vue semantics
122+
- templates and scripts remain disconnected
123+
- no path to proper diagnostics or navigation
124+
125+
This is insufficient for the requested outcome.
126+
127+
### Option 2: Treat `.vue` as TypeScript-family and reuse `typescript-language-server`
128+
129+
Pros:
130+
131+
- reuses an existing bundled LSP dependency
132+
- may provide some script-block behavior
133+
134+
Cons:
135+
136+
- does not correctly model Vue SFCs
137+
- templates, props, emits, and generated type relationships are unreliable
138+
- conflates Vue support with TypeScript worker assumptions
139+
140+
This is architecturally incorrect for a Vue editor feature.
141+
142+
### Option 3: Dedicated Monaco `vue` + dedicated Volar server
143+
144+
Pros:
145+
146+
- aligns editor language id and server kind cleanly
147+
- fits the existing lazy LSP architecture
148+
- gives Vue SFCs the correct semantic engine
149+
- keeps future expansion straightforward
150+
151+
Cons:
152+
153+
- requires new package dependency and managed-install support
154+
- touches both frontend and backend language maps
155+
156+
This is the recommended design.
157+
158+
## Frontend Design
159+
160+
### Language Detection
161+
162+
`packages/web/src/features/code-editor/components/monaco-host.tsx`
163+
164+
Update the editor language detector so:
165+
166+
- `.vue` resolves to `vue`
167+
- LSP language detection returns `vue` unchanged
168+
169+
The current JSX and TSX special-casing should remain intact.
170+
171+
### Monaco Language Registration
172+
173+
Add a small Monaco Vue language registration module under the code editor feature, for example:
174+
175+
- `packages/web/src/features/code-editor/monaco/vue-language.ts`
176+
177+
Responsibilities:
178+
179+
- register Monaco language id `vue`
180+
- register tokenizer and configuration once
181+
- export an idempotent `ensureVueLanguageRegistered()` helper used by the editor host bootstrap path
182+
183+
This keeps Vue-specific editor setup isolated from the main React component.
184+
185+
### LSP Bridge Language Mapping
186+
187+
`packages/web/src/features/code-editor/lsp/language-map.ts`
188+
189+
Extend the bridge-side LSP server-kind resolver so:
190+
191+
- `.vue` files resolve to `vue`
192+
- `monacoLanguage === "vue"` resolves to `vue`
193+
194+
`packages/web/src/features/code-editor/lsp/bridge.ts`
195+
196+
The provider registration path should register Monaco providers directly for `vue`. No language-id folding is needed for Vue, unlike `typescriptreact` and `javascriptreact`.
197+
198+
### UX Expectations
199+
200+
The editor should behave like existing supported LSP languages:
201+
202+
- no visible notice when Vue is supported and ready
203+
- inline missing-tool notice when the Vue LSP is unavailable
204+
- install and retry actions reuse the existing notice mechanics
205+
206+
## Backend Design
207+
208+
### Core Domain
209+
210+
`packages/core/src/domain/lsp.ts`
211+
212+
Extend `LspServerKind` to include:
213+
214+
- `vue`
215+
216+
Any shared schemas or discriminated unions derived from `LspServerKind` must stay exhaustive after this change.
217+
218+
### Server-Kind Resolution
219+
220+
`packages/server/src/lsp/server-factory.ts`
221+
222+
Extend path-based resolution so:
223+
224+
- `.vue` maps to `vue`
225+
226+
This preserves the existing separation between file-extension routing and process launch configuration.
227+
228+
### Tool Definition
229+
230+
`packages/server/src/lsp-tools/definitions.ts`
231+
232+
Add a new `vue` definition using `@vue/language-server`.
233+
234+
The definition should include:
235+
236+
- `serverKind: "vue"`
237+
- a user-facing display name such as `Vue language server`
238+
- default command and stdio args suitable for Volar
239+
- managed install metadata
240+
241+
Vue should not be bundled in the same way TypeScript is currently bundled. It should use the managed install path so the product behavior matches Python, Go, and Rust: explicit installation, structured status, and recovery UI.
242+
243+
### Managed Install
244+
245+
`packages/server/src/lsp-tools/install-manager.ts`
246+
247+
Extend managed install planning and executable resolution to support `vue`.
248+
249+
The install contract should:
250+
251+
- install `@vue/language-server` into the managed tools root under a versioned directory
252+
- produce a stable executable path recorded in the manifest
253+
- verify the executable exists before reporting success
254+
255+
If Volar requires a Node-based CLI entrypoint rather than a native binary, the manifest and execution path should support launching it through the current Node runtime in the same style already used for bundled TypeScript CLI resolution.
256+
257+
### Command Surface
258+
259+
`packages/server/src/commands/lsp.ts`
260+
261+
Extend any `z.enum([...])` validation that currently enumerates installable server kinds so `vue` is accepted.
262+
263+
This ensures frontend install actions can request Vue tool installation through the same API shape used today.
264+
265+
## Package and Runtime Dependencies
266+
267+
### Server Dependencies
268+
269+
`packages/server/package.json`
270+
271+
Add the Vue language server package required by the chosen launch strategy:
272+
273+
- `@vue/language-server`
274+
275+
If Volar requires additional runtime companions for stable operation, add only the minimum required packages and document why in the dependency comment or surrounding implementation notes.
276+
277+
### No Frontend Framework Runtime Dependency
278+
279+
The web package should not pull in the Vue framework just to highlight or edit `.vue` files. Monaco registration and LSP transport are sufficient.
280+
281+
## Failure Handling
282+
283+
The feature must preserve current degraded-state behavior:
284+
285+
- unsupported or missing Vue tooling must not break file opening
286+
- missing prerequisites must produce structured LSP state
287+
- failed install attempts must remain retryable
288+
- LSP startup failure must degrade to ordinary editing plus notice
289+
290+
The system must not:
291+
292+
- crash the editor when a Vue file opens
293+
- silently misroute `.vue` to the TypeScript server
294+
- report `unsupported_language` for `.vue` after the feature ships
295+
296+
## Testing Strategy
297+
298+
### Frontend Tests
299+
300+
Add or update tests for:
301+
302+
- `.vue` editor language detection
303+
- standalone and workspace-backed model creation using `vue`
304+
- LSP bridge language resolution for Vue
305+
- provider registration and attach flow for `monacoLanguage: "vue"`
306+
307+
Primary files likely affected:
308+
309+
- `packages/web/src/features/code-editor/components/monaco-host.test.tsx`
310+
- `packages/web/src/features/code-editor/lsp/bridge.test.tsx`
311+
- `packages/web/src/features/code-editor/lsp/providers.test.ts`
312+
- new focused tests for the Vue Monaco registration module
313+
314+
### Backend Tests
315+
316+
Add or update tests for:
317+
318+
- `LspServerKind` exhaustiveness where relevant
319+
- `.vue` path resolution in `server-factory`
320+
- Vue tool definition lookup
321+
- Vue install-manager planning and verification
322+
- LSP command schema accepting `vue`
323+
- `ensureSession()` returning `unsupported_language` no longer applies to `.vue`
324+
325+
Primary files likely affected:
326+
327+
- `packages/server/src/lsp/server-factory.test.ts`
328+
- `packages/server/src/lsp-tools/manager.test.ts`
329+
- `packages/server/src/lsp-tools/install-manager.test.ts`
330+
- `packages/server/src/__tests__/lsp-commands.test.ts`
331+
- `packages/server/src/lsp/manager.test.ts`
332+
333+
## Implementation Notes
334+
335+
### File Boundaries
336+
337+
Recommended new or expanded units:
338+
339+
- `packages/web/src/features/code-editor/monaco/vue-language.ts`
340+
- Vue Monaco registration only
341+
- `packages/web/src/features/code-editor/components/monaco-host.tsx`
342+
- detect and apply `vue` language ids
343+
- `packages/web/src/features/code-editor/lsp/language-map.ts`
344+
- map `.vue` and `vue` to `LspServerKind.vue`
345+
- `packages/core/src/domain/lsp.ts`
346+
- extend shared server-kind type
347+
- `packages/server/src/lsp/server-factory.ts`
348+
- resolve `.vue` to `vue`
349+
- `packages/server/src/lsp-tools/definitions.ts`
350+
- describe Vue tool ownership and launch strategy
351+
- `packages/server/src/lsp-tools/install-manager.ts`
352+
- install and verify the Vue language server
353+
354+
### Launch Strategy Constraint
355+
356+
The implementation must validate the exact Volar CLI entrypoint shape before coding the install path. The design decision is fixed: use `@vue/language-server` through the managed-tool system. The only implementation choice left is whether the manifest stores:
357+
358+
- a direct executable shim produced by package installation
359+
- or a Node-launched entry script path plus args
360+
361+
That choice should be determined from the installed package structure during implementation and then covered by tests.
362+
363+
## Rollout Outcome
364+
365+
After this work:
366+
367+
- `.vue` files open with Vue-aware syntax highlighting
368+
- Vue files participate in the same lazy LSP lifecycle as other supported languages
369+
- Volar powers diagnostics and navigation for Vue SFCs
370+
- install and recovery behavior matches the existing managed LSP UX
371+
372+
The result is a consistent product story: Vue becomes a first-class editor language instead of a plain-text exception.

0 commit comments

Comments
 (0)