Skip to content

fix(server): drop fs dependency (#152)#156

Merged
pranaygp merged 1 commit into
masterfrom
worktree-agent-ae6f78a2c53ab8a07
May 17, 2026
Merged

fix(server): drop fs dependency (#152)#156
pranaygp merged 1 commit into
masterfrom
worktree-agent-ae6f78a2c53ab8a07

Conversation

@pranaygp
Copy link
Copy Markdown
Owner

Summary

  • Move stylesheet I/O from the server to the client. The client reads each discovered stylesheet via vscode.workspace.fs.readFile (virtual/web-workspace safe) and sends {uri, languageId, text} tuples to the server during LSP initialization.
  • Server no longer imports fs and never touches the host file system, so the LSP backend works in virtual workspaces and on the web build.
  • Bump virtualWorkspaces.supported to true in the extension manifest now that the prerequisite is gone.

Test plan

  • yarn build (zero TS errors)
  • yarn test (25 passing, including new stylesheets loaded via vscode.workspace.fs are discoverable case in tests/src/findDefinition.test.ts)
  • yarn lint
  • yarn prettier on changed files

Closes #152

Copilot AI review requested due to automatic review settings May 16, 2026 20:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR moves stylesheet content loading from the language server to the VS Code client so the server no longer reads from the host filesystem, and updates the manifest to advertise virtual workspace support.

Changes:

  • Client discovers stylesheets and sends { uri, languageId, text } initialization payloads.
  • Server consumes stylesheet text directly instead of importing/using fs.
  • Tests add a workspace-fs-based stylesheet loading path.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
client/src/extension.ts Reads discovered stylesheet contents via vscode.workspace.fs and passes them to the server.
server/src/server.ts Builds the initial stylesheet map from client-provided stylesheet payloads.
server/src/types.ts Replaces the old URI/fsPath initialization shape with a stylesheet text payload type.
tests/src/findDefinition.test.ts Adds coverage for stylesheet maps built from vscode.workspace.fs.readFile.
package.json Marks virtual workspaces as supported.
Comments suppressed due to low confidence (3)

client/src/extension.ts:186

  • This new use of workspace.fs requires a newer VS Code API than the top-level manifest currently advertises (engines.vscode is still ^1.33.0). Users on supported older VS Code versions can install the extension, but this call will fail and the server will start with an empty stylesheet map unless the engine range is bumped or a fallback is kept.
                  const bytes = await Workspace.fs.readFile(u);

client/src/extension.ts:190

  • Buffer is a Node global, but this extension also declares a browser entrypoint and the surrounding comments claim web compatibility. In the browser extension host this decode step can throw before any stylesheet is sent; use a browser-safe decoder or provide a polyfill if web support is intended.
                    text: Buffer.from(bytes).toString("utf8"),

client/src/extension.ts:184

  • Reading every discovered stylesheet with an unbounded Promise.all can issue thousands of concurrent reads and keep both byte buffers and decoded strings in memory before the language client starts. Large or remote/virtual workspaces can stall activation or exhaust memory; consider bounded concurrency and/or a lazy/on-demand loading protocol.
          const stylesheets: Stylesheet[] = (
            await Promise.all(
              file_searches.map(async (u): Promise<Stylesheet | null> => {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json Outdated
"virtualWorkspaces": {
"supported": false,
"description": "The extension currently relies on the `fs` module but it should be easy to change this. Please make a PR to help."
"supported": true
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — reverted in 02868fe. The documentSelector and the scheme guard at the top of didOpenTextDocument still gate on file/untitled, so advertising virtual workspace support here was premature even though the server side is ready. Expanding the selector to cover vscode-vfs, vscode-test-web, etc. is a separate follow-up; this PR's scope is just dropping fs.

Comment thread client/src/extension.ts Outdated
const potentialFiles: Uri[] = file_searches.filter(
(uri: Uri) => uri.scheme === "file"
);
).then(async (file_searches) => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Right — fixed in 02868fe. Added a pendingClientFolders: Set<string> that's claimed synchronously before any await, so concurrent didOpenTextDocument calls in the same folder see the in-flight client and bail. The continuation also bails out if the folder was removed (workspaceFolders change) while reads were in progress. Same shape as the fix in PR #155 for issue #154 — when both land they'll converge on the same pattern.

assert.deepStrictEqual(lines, [0, 16, 16, 19]);
});

test("stylesheets loaded via vscode.workspace.fs are discoverable", async () => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fair. Added a new test resolves selectors across non-file URI schemes in 02868fe that constructs a StylesheetMap with vscode-vfs:// and vscode-test-web:// URIs and verifies findDefinition resolves selectors and returns Locations on those schemes. That nails down the scheme-agnosticism contract on the server side. (The client's documentSelector still gates on file/untitled, so the full virtual-workspace flow needs separate work — see the related thread above.)

Squashed: includes the original fs-removal commit and the follow-up
review fixes (TextDecoder for browser compat, bounded read concurrency,
race-guard during async file reads, virtualWorkspaces.supported reverted
to false until documentSelector is broadened, engines.vscode bumped to
the workspace.fs minimum).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp pranaygp force-pushed the worktree-agent-ae6f78a2c53ab8a07 branch from 02868fe to 8fa4abe Compare May 17, 2026 00:50
@pranaygp pranaygp merged commit f198a58 into master May 17, 2026
2 checks passed
@pranaygp pranaygp deleted the worktree-agent-ae6f78a2c53ab8a07 branch May 17, 2026 00:51
pranaygp added a commit that referenced this pull request May 17, 2026
Builds on top of #156's pendingClientFolders race guard with three small
robustness improvements identified in the #155 review:

- Register the LanguageClient in `clients` BEFORE calling start(). If a
  workspace folder is removed mid-start, onDidChangeWorkspaceFolders can
  now find and stop the client.
- Wrap client.start() in try/catch; on failure remove from `clients` so
  the folder can be retried on the next document-open.
- Wrap Workspace.findFiles() in try/catch and log via telemetry instead
  of letting the rejection bubble up as an unhandled promise (the outer
  call site swallows it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pranaygp added a commit that referenced this pull request May 17, 2026
Builds on top of #156's pendingClientFolders race guard with three small
robustness improvements identified in the #155 review:

- Register the LanguageClient in `clients` BEFORE calling start(). If a
  workspace folder is removed mid-start, onDidChangeWorkspaceFolders can
  now find and stop the client.
- Wrap client.start() in try/catch; on failure remove from `clients` so
  the folder can be retried on the next document-open.
- Wrap Workspace.findFiles() in try/catch and log via telemetry instead
  of letting the rejection bubble up as an unhandled promise (the outer
  call site swallows it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pranaygp added a commit that referenced this pull request May 17, 2026
Builds on top of #156's pendingClientFolders race guard with three small
robustness improvements identified in the #155 review:

- Register the LanguageClient in `clients` BEFORE calling start(). If a
  workspace folder is removed mid-start, onDidChangeWorkspaceFolders can
  now find and stop the client.
- Wrap client.start() in try/catch; on failure remove from `clients` so
  the folder can be retried on the next document-open.
- Wrap Workspace.findFiles() in try/catch and log via telemetry instead
  of letting the rejection bubble up as an unhandled promise (the outer
  call site swallows it).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

The extension currently relies on the fs module but it should be easy to change this. Please make a PR to help.

2 participants