小橙子 96f578e5d3
feat: LSP manager + status indicator foundation (Phase 3) (#26)
* feat(server): LSP foundation infra — feature flag, JSON-RPC framer, process limits

Phase 3 base layer (tasks 18-20 of editor-lsp-foundation):

- src/server/services/lspFeatureFlag.ts: local opt-in switch.
  spec called for `feature('LSP_FOUNDATION')` from `bun:bundle`, but
  that table is build-time vendored from upstream Anthropic CLI and
  fork code can't register new flag names there. So Phase 3 ships its
  own check: dev (NODE_ENV !== 'production') is on by default; prod
  is opt-in via `CLAUDE_CODE_LSP=1` env var. Pure function so unit
  tests can stub `process.env` directly. 5/5 tests pass.

- src/server/services/lspJsonRpc.ts: minimal Content-Length-framed
  JSON-RPC. encodeLspFrame(payload) emits the wire bytes;
  LspFrameDecoder accepts arbitrary stream chunks (LSP servers split
  frames mid-header / mid-body / multiple-per-chunk) and yields
  complete frames per push(). Throws LspFrameDecodeError on missing
  or non-numeric Content-Length. ~110 lines, no vscode-jsonrpc
  dependency. 10/10 tests pass.

- src/server/services/lspProcessLimits.ts: poll-fallback strategy
  (5 s sample interval, terminate after 2 consecutive overages).
  spec also lists posix-rlimit and windows-job-object — those need
  native bindings or N-API plugins we don't carry yet, so this PR
  ships poll-fallback only and exposes a stable LspProcessLimits
  interface so a future PR can plug in native strategies without
  touching call sites. Sampler is abstract (manager passes a real
  ps/Get-Process wrapper in production; tests pass a synthetic
  array). 5/5 tests pass.

Tested:
- bun test src/server/services/lspFeatureFlag.test.ts (5/5)
- bun test src/server/services/lspJsonRpc.test.ts (10/10)
- bun test src/server/services/lspProcessLimits.test.ts (5/5)

_Requirements: 7.2, 7.4, 15.3 (adapted)_

* feat(server,desktop): LspManager + LspStatusIndicator (Phase 3 wave 2)

Phase 3 main pieces (tasks 21-27 of editor-lsp-foundation):

Server:
- src/server/services/lspManager.ts: LspManager service. Per-workspace
  lazy spawn via injected LspClientFactory; sha-clean state machine
  (starting -> ready -> unavailable with 5 reasons). mapLspSeverity
  maps LSP wire severity 1-4 -> error/warning/info/hint, anything
  else -> error. makeDiagnosticComparator orders by severity ->
  in-edited-file -> path -> line -> column. truncateDiagnostics
  caps at top-20 entries / 5 distinct files. Idle eviction
  (default 600 s) + restart cap (2 attempts in 60 s rolling window
  -> restart-cap-exhausted). probeHostCommand from prerequisitesService
  is reused.
- src/server/services/lspManager.test.ts: 18 tests covering severity
  map / comparator / truncation / spawnIfNeeded gate / prereq-missing /
  init-failed / ready+diagnostics flow / shutdownWorkspace /
  onStateChange listeners / getPrerequisites delegation / sorted+
  truncated diagnostics on real flow.
- src/server/events/lspStateChanged.ts: type-only WS event shape
  for `lsp.state.changed` so producers and consumers stay aligned.
- src/server/api/sessions.ts: GET /api/sessions/:id/lsp/state. Gated
  by isLspFeatureEnabled() — returns 404 FEATURE_DISABLED in
  production unless CLAUDE_CODE_LSP=1.

Desktop:
- desktop/src/types/lsp.ts: desktop-side mirror of LSP wire types.
  Mirrored manually rather than imported across boundaries because
  there's no shared types module yet.
- desktop/src/components/workspace/LspStatusIndicator.tsx: status
  pill + dropdown. CheckCircle / AlertCircle / Loader2 / AlertTriangle
  via lucide-react (already a dep). Labels: "Ready" / "N errors
  detected" / "9999+ errors detected" cap / "Starting language
  server…" / "Language server unavailable". Single context action:
  prereq-missing -> Install... button; other unavailable -> Retry.
  Dropdown lists diagnostics with messages truncated at 200 chars,
  empty list shows "No diagnostics". Keyboard a11y (Tab/Enter/Space/
  ArrowUp/ArrowDown/Esc) + aria-live polite mirror.
- desktop/src/components/workspace/LspStatusIndicator.test.tsx: 11
  RTL tests covering all five state visuals + error count cap +
  Install vs Retry action gating + dropdown empty state + message
  truncation + keyboard activation + aria-live mirror.

Stability fix:
- src/server/services/lspProcessLimits.test.ts: bumped sleep from 20 ms
  to 60 ms in the "swallows sampler error" test — Windows + bun
  scheduler occasionally fails to fire 5 ms intervals twice within
  20 ms. 5/5 stable now.

Tested:
- bun test src/server/services/lspFeatureFlag.test.ts (5/5)
- bun test src/server/services/lspJsonRpc.test.ts (10/10)
- bun test src/server/services/lspProcessLimits.test.ts (5/5)
- bun test src/server/services/lspManager.test.ts (18/18)
- bun run test src/components/workspace/LspStatusIndicator.test.tsx (11/11)
- bun run lint (tsc --noEmit clean, both desktop and server)
- bun run check:bundle-budget (delta +0.33 KB gz, 99.67 KB headroom)
- bun run build (clean, 1.27s)

Note: pre-existing e2e/business-flow failures (~44) on origin/main
baseline remain untouched by this change.

_Requirements: 6.1-6.5, 7.1-7.6, 9.1-9.5, 12.1-12.4, 13.1-13.10, 15.1, 15.3_

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 08:06:32 +08:00
..

Claude Code Haha Desktop

基于 Tauri 2 + React 的桌面客户端。

开发

bun install
bun run tauri dev

构建

# macOS (Apple Silicon)
./scripts/build-macos-arm64.sh

# Windows (x64, MSI only)
.\scripts\build-windows-x64.ps1

构建产物位于 build-artifacts/ 目录,文件名会显式包含平台、架构和包类型。

常见问题

macOS 提示"已损坏,无法打开"

xattr -cr /Applications/Claude\ Code\ Haha.app