8.4 KiB
spec, created, status
| spec | created | status |
|---|---|---|
| docs/specs/lsp-file-intelligence/spec.md | 2026-07-28 | completed |
Implementation Plan: LSP Initialization Status and Placement
Overview
Repair the misleading infinite initialization presentation without imposing a timeout on legitimate cold Gradle imports. First make the launched-process and long-running initialize states testable, then add the portable placement contract through the existing user-settings path, reuse the active editor's status surface in the application status bar, and finish with production-build desktop/tablet/phone E2E and public documentation.
Confirmed root cause
agentctl emits its ready bridge status immediately after StartPipedProcess succeeds. The browser then records initializingSince and awaits JsonRpcConnection.sendRequest("initialize", ...), which intentionally has no timeout. Kotlin LSP performs Gradle project import during that request and can take minutes or fail to return for some multi-module projects, so the process is alive while Kandev remains in starting. The current “Preparing project…” copy does not expose that distinction and never promotes a long-running wait into actionable guidance.
Smallest deterministic reproduction: install the existing fake Kotlin server with holdInitialize: true, open a Kotlin file, start LSP, and never create the release sentinel. The task-host process starts and receives initialize, while the browser remains in starting indefinitely.
Backend
Portable LSP status location
- Add
LspStatusLocation stringtoapps/backend/internal/user/models/models.go, withtoolbarandstatus_barconstants plus normalization totoolbar. - Carry
lsp_status_locationthrough response and pointer-valued PATCH DTOs inapps/backend/internal/user/dto/dto.go, controller/service request mapping,applyBasicSettings, anduser.settings.updated. - Persist the value inside the existing user-settings JSON in
apps/backend/internal/user/store/sqlite.go; no relational migration or endpoint is needed. - Map it into the Go-served SPA boot state as
lspStatusLocationinapps/backend/internal/backendapp/boot_state_routes.go.
Frontend
Honest initialization stages
- Extend
apps/web/lib/lsp/lsp-progress-view.tswith a 60-second long-running threshold and pure presentation state that distinguishes “process started, initialize pending” from LSP readiness. - Preserve the live connection and Stop action at every duration. Kotlin-specific copy may name Gradle import as a possible cause but must not claim indexing, percentage, completion, or ETA.
- Refactor
apps/web/components/editors/lsp-status-button.tsxonly as needed to share the same details and summary between toolbar and status-bar triggers.
User setting and effective fallback
- Add the camel-case
lspStatusLocationvalue to the settings slice, SSR/boot mapping, WS update handler, API types, carry-forward helpers, and Editors draft/save baseline. - Add a self-documenting placement choice to Settings > Editors.
toolbaris the default.status_barexplains that it requires the opt-in Application status bar and a fine pointer; unsupported runtime conditions use the toolbar without rewriting the saved preference. - Add a pure placement helper so the toolbar and status item cannot disagree about the effective surface.
Active-editor application status item
- Add
builtin:lspto the existing opaque, reorderable application status items. - Derive the item from
activeSessionId, the active Dockview panel and loaded text buffer, andgetMonacoLanguage; loading, static/binary, diff, unsupported, and non-file panels render no item even when the saved editor provider is Monaco. - Reuse the browser-owned
(session, language)LSP connection and lifecycle control. The compact bar summary shows the active language plus readiness, elapsed initialization, or server percentage when available; opening it uses the same anchored details popover. - Do not mount a phone LSP lease. Coarse-pointer Monaco layouts keep the existing touch-sized toolbar trigger and bottom drawer.
Tests
- Long-running initialize presentation:
apps/web/lib/lsp/lsp-progress-view.test.tscovers just below and at 60 seconds, Kotlin Gradle guidance, elapsed time, no ETA, and unchanged lifecycle action. - Placement resolution: a focused pure-helper test covers default toolbar, enabled fine-pointer status bar, feature-disabled fallback, coarse-pointer fallback, and phone exclusion.
- Portable backend contract: focused tests in
apps/backend/internal/user/{dto,service,store}cover default normalization, valid round-trip, invalid PATCH rejection, omission semantics, and event payload. - Boot and client hydration:
apps/backend/internal/backendapp/boot_state_user_settings_test.go,apps/web/lib/ssr/user-settings.test.ts, andapps/web/lib/ws/handlers/users.test.tscover missing, saved, and live-updated values. - Editors draft:
apps/web/components/settings/settings-dirty.test.tsand focused settings-state tests cover dirty comparison, PATCH payload, saved baseline, and response mapping. - Status item: focused app-status tests cover
builtin:lspordering identity, active supported file summary, hidden loading/static/diff/unsupported/non-file states, and lifecycle disclosure reuse.
E2E Tests
- Slow Kotlin initialization: extend
apps/web/e2e/tests/lsp/lsp-file-intelligence.spec.ts; a held fake initialize proves the process-started stage immediately and the long-running warning after advancing browser time, with Stop still enabled and no ETA. - Placement persistence and behavior: the same desktop spec changes the Editors setting to
status_bar, saves, opens a Kotlin file, proves the toolbar control is absent, operates LSP frombuiltin:lsp, reloads, and proves the preference persists. - Active-file scoping: switch from the Kotlin editor to binary Kotlin, unsupported text, and non-file panels and back; the status item hides and restores without exposing an inert lifecycle action or starting a different server.
- Tablet fallback: extend
apps/web/e2e/tests/lsp/mobile-lsp-file-intelligence.spec.tswith savedstatus_bar; the coarse-pointer tablet still gets the 44px toolbar trigger and contained LSP drawer. - Phone boundary: save
status_barplus Kotlin auto-start, open Kotlin in the phone viewer, and prove no LSP control, status item, WebSocket, or process appears. - Restore all user settings changed by a spec so the worker-scoped backend does not leak preferences.
Mobile Design Contract
- Desktop outcome: a fine-pointer user chooses toolbar or application status bar and opens the same active-editor LSP details.
- Mobile entry point: phone has none because CodeMirror remains LSP-free. Coarse-pointer tablet uses the existing Monaco toolbar control.
- Nearest exemplars:
AppStatusBarprovides the persistent fine-pointer item and opaque ordering; the shipped LSP tablet drawer provides the touch surface. - Hierarchy and action: the bar shows language plus one live summary; disclosure shows connection stage, project work, elapsed time, and one Start/Stop/Retry action.
- Surface rationale: a 24px application bar suits glanceable desktop status, while the coarse-pointer bar cannot provide a 44px target, so tablet falls back to the toolbar drawer.
- Geometry: tablet retains one
max-h-[80dvh]internal scroll owner, safe-area-aware shared drawer behavior, 44px trigger/actions, and no document horizontal overflow. - Shared logic: status/progress snapshots, placement resolution, summary formatting, and lifecycle actions are shared; only the trigger composition differs.
- Responsive persistence: runtime fallbacks never overwrite
lsp_status_location.
Public documentation
- Update
docs/public/developer-tools.mdanddocs/public/feature-status.mdwith the launched-process/initialize distinction, Kotlin Gradle-import caveat, no automatic timeout/ETA, and placement fallback behavior. - Run both public-doc validators.
Implementation Waves And Parallel Candidates
Execution is sequential in the primary conversation because the tasks share the LSP presentation contract and user-settings types.