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