Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 2aa5cf1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
✅ No new issues found.
This adds the version 1 editor configuration protocol cleanly: strict envelope/version/source checks, normalization (dedupe/trim, non-string entries skipped with logs), resource limits enforced with reportResourceLimits, and editor extraTags are appended on top of project config. Legacy configurePlugin payloads keep full-replacement behavior. EditorConfigurationManager and ProjectConfigurationManager wiring is exception-safe (logging best-effort, no exceptions leak into tsserver), handles before/after project creation, and shares state correctly across projects in one host. Exports/types/docs/changeset/tests all look consistent. Ready to merge as-is.
big-pickle (free) | 𝕏
There was a problem hiding this comment.
🟡 Changes recommended
Clean-checkout typechecking is blocked, revoked configuration Proxies can abort activation, and shared-listener lifecycle memory coverage is missing.
3 open issues
What changed in this PR
Adds protocol v1 so editor integrations can append styled-template tags without replacing project settings, while preserving legacy runtime configuration behavior.
Changes:
- Introduces bounded editor-tag overlays shared within a tsserver ProjectService.
- Exports protocol types and a version constant, with integration documentation.
- Adds configuration, lifecycle, package API, and performance coverage; updates dependencies.
| File | Description |
|---|---|
yarn.lock |
Records dependency updates. |
test/unit/tsserver-plugin.test.ts |
Tests configuration arrival order, sharing, and isolation. |
test/unit/plugin-configuration.test.ts |
Tests normalization, overlays, limits, and hostile inputs. |
test/performance/template-language-service.bench.ts |
Benchmarks matching the last allowed editor tag. |
test/performance/scaling-check.ts |
Adds maximum-tag matching checks. |
test/package-api/tsconfig.json |
Includes the CommonJS API consumer in typechecking. |
test/package-api/runtime.ts |
Checks the exported protocol version. |
test/package-api/runtime-esm-consumer.mjs |
Checks the protocol version through ESM imports. |
test/package-api/consumer.ts |
Exercises public protocol types. |
test/package-api/consumer-api.cts |
Exercises the CommonJS protocol API. |
test/e2e/scenarios/plugin-lifecycle.test.ts |
Tests editor overlays through real tsserver requests. |
test/e2e/fixture-paths.ts |
Registers the editor-configuration fixture. |
test/e2e/editor-configuration-project-fixture/tsconfig.json |
Defines project settings for overlay tests. |
test/e2e/editor-configuration-project-fixture/main.ts |
Provides the fixture’s source file. |
src/tsserver/tsserver-plugin.ts |
Restores project bases and coordinates dynamic configuration. |
src/tsserver/tsserver-logger.ts |
Contains host logging failures. |
src/tsserver/plugin-identity.ts |
Adds the package-name constant. |
src/tsserver/editor-configuration.ts |
Implements protocol parsing, shared state, and tag merging. |
src/index.ts |
Scopes shared configuration to each ProjectService. |
src/configuration/plugin-configuration.ts |
Defines protocol types and hardens configuration copying. |
src/api.ts |
Exports protocol types and version. |
package.json |
Updates runtime and development dependencies. |
docs/usage.md |
Explains editor overlays and replacement behavior. |
docs/tsserver-host.md |
Documents project-base recovery during activation. |
docs/maintenance.md |
Adds protocol testing guidance. |
docs/architecture.md |
Specifies configuration state and lifecycle invariants. |
.changeset/quiet-tags-overlay.md |
Declares the additive capability as a minor release. |
Review effort: Balanced
|
@pullfrog review |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Editor configuration protocol v1 — Adds
editorConfigurationenvelope support toconfigurePlugin, with normalized parsing, resource limits, and mutual exclusivity with legacy replacement; mergesextraTagsinto project tags case-sensitively with deduplication. - Configuration state management — Introduces
EditorConfigurationManagerand extendsProjectConfigurationManagerto track dynamic configuration state (editor vs legacy), applying overlays to the project base and handling last-kind-wins across sources. - tsserver integration — Updates
StyledPluginto coordinate editor/project configuration lifecycle, recover project base fromcompilerOptions.plugins(with resilience if reading fails), and handle pre-project payloads correctly. - Public API and docs — Exports
EDITOR_CONFIGURATION_PROTOCOL_VERSIONand editor configuration types from./api, documents the protocol in usage/architecture/tsserver-host/maintenance docs. - Tests and fixtures — Adds e2e scenarios for appending/clearing editor tags, pre-project payloads, kind switching, and unit/probe coverage for lifecycle behavior.
big-pickle (free) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior Pullfrog review)
- Completed editor configuration lifecycle — Hardened activation around revoked/hostile configs (e.g. guarded
Array.isArrayaccess) and corrected legacy activation paths so overlays/legacy replacements behave correctly across reloads and project creation order. - Memory/lifecycle coverage — Added
test/unit/editor-configuration-lifecycle.probe.tsexercising weak-reference cleanup for discarded projects/listeners with GC and a surviving-project control; extended lifecycle tests. - Package API typecheck scoping — Removed
test/package-api/consumer-api.ctsfrom the source typechecktsconfigso clean typechecking works before build (post-build consumers still covered). - CI/release gates and docs — Updated CI/release/docs/changeset as part of completing the protocol lifecycle.
The incremental changes address all previously flagged issues. Existing behavior remains backward compatible and tests/fixtures look consistent.
big-pickle (free) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Changes since the prior pullfrog review (1911d3e), one test-tooling commit:
- Enlarged N for coarse-clock text checks — Raises the fixture size from 6,000 to 24,000 for the four substitution and escape cases whose N timing sits near the 1 ms CPU clock quantum, so a quantized
0.000msN sample no longer makes the ratio unmeasurable. - Threaded a per-case N through the check factory — Adds an optional
ntoTextCheckCaseanddefineTextCheck, defaulting toSUBSTITUTION_CHECK_Nso untouched cases keep their size.
Filtered runs of all four affected checks pass with ratios from 3.3 to 4.5, inside MIN_PLAUSIBLE_RATIO and SCALING_THRESHOLD, and finish well under the per-check deadline.
big-pickle (free) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Changes since the prior pullfrog review (cb08bf8), all in test tooling:
- E2E cleanup timeout headroom — Derives the e2e
hookTimeoutfromTSSERVER_RESPONSE_TIMEOUT_MSplus 15 s soonTestFinished/afterAllcleanup can surface the harness's process-exit timeout instead of Vitest's generic hook timeout. - Bounded response timeout — Caps the parsed response timeout at
MAX_TIMER_DELAY_MS - E2E_TIMEOUT_HEADROOM_MS, keeping the derived hook and test timeouts within Node's maximum timer delay. - Config extraction — Exports
e2eTimeoutMs,e2eTestConfig,e2eProjectConfig, andvitestConfigso a unit test can assert the e2e project wiring. - Coverage and docs — Adds unit tests for the capped parser and the Vitest project wiring, and documents the hook-timeout relationship in
docs/maintenance.md. - Changeset — Adds a patch changeset for the cleanup-hook headroom.
Verification: yarn typecheck, the two new unit files, oxlint, and oxfmt --check pass. Vitest's runner uses runner.config.hookTimeout for onTestFinished, so the raised value governs per-test server cleanup as intended.
big-pickle (free) | 𝕏
quantizor
left a comment
There was a problem hiding this comment.
Thanks, the core is right and I want it in. Appending editor tags on top of the project's tags is the fix, and the tsserver lifecycle handling is solid.
I installed the packed build in VS Code and stepped through sending editor tags, clearing them, sending a legacy payload, and sending tags again. Each state applied correctly, completion and hover work inside the added tag, and untagged templates stay untouched. I also compared it against main with a real tsserver on outline/outline (1,033 styled templates) using typing, completion, and file switch traces. There's no measurable change in latency, cold start, or heap, and no leak.
Before it ships as a minor, please cut it down, since src/api.ts becomes public contract on publish:
- Drop
protocolVersion,EDITOR_CONFIGURATION_PROTOCOL_VERSION, andsource. TheeditorConfigurationkey is enough of a discriminator, and a version can come when a second shape exists. - Drop the tag count and length caps. Skip bad entries like non-strings are skipped today.
- Replace the deep clone with a one-level copy, and drop the proxy and gc probe tests,
FinalizationRegistry, anddeactivate. - Keep the previous overlay when every entry in an array is rejected. Only a real
[]should clear. - Treat a differing project entry as a legacy replace only when its settings actually differ.
- Call
project.refreshDiagnostics()on each live project after a config change is applied. Today open files keep stale diagnostics until the next edit, because tsserver never emitsprojectsUpdatedInBackgroundafterconfigurePlugin. Main has the same gap, but this PR is what makes runtime config changes a normal editor flow, so it matters now. - Note in the docs that an extra tag is only a match string, so
globalStylewon't parse as a global stylesheet. - Move the dependency bumps, CI and release workflow changes, and the hook timeout changeset to separate PRs.
Then update the phase 7 spec to match this PR before the VS Code side is built.
|
The model never responded, so Pullfrog stopped this run after 2 minutes. The request went out but nothing came back. This is usually temporary, so re-trigger to try again. Last agent output
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior Pullfrog review)
- Simplified editor configuration protocol — Removed
protocolVersion,source,EDITOR_CONFIGURATION_PROTOCOL_VERSION, and resource limits from the protocol. TheeditorConfigurationkey withextraTagsis sufficient for discrimination. Bad entries (non-strings) are skipped; an empty array explicitly clears the overlay, while a non-empty array with all entries skipped preserves the previous overlay. - Diagnostics refresh on effective changes —
EditorConfigurationManagernow callsproject.refreshDiagnostics()on each live project after applying effective configuration changes (deduplicated per project per update). Both legacy and editor updates refresh diagnostics when they actually change effective settings, and failures in individual projects are logged without affecting others. - Normalized diffing and safer legacy detection — Legacy configuration overrides are only treated as replacements when normalized settings actually differ; shared state comparisons use deep equality and avoid unnecessary notifications/caches/diagnostic refreshes.
- Tightened config cloning and storage — Replaced deep cloning with one-level copies for lint/Emmet objects, removed
FinalizationRegistry/gc probes/deep hostile input guards and related tests, and streamlined the configuration lifecycle. - Docs and tests updated — Documentation now clarifies extra tags are match strings only (no global stylesheet parsing mode), and tests/fixtures were updated to match the simplified protocol.
The latest commits address all points raised in the human review. No new issues found.
big-pickle (free) | 𝕏
|
@quantizor Thank you for the thoughtful review. I’ve added a public Editor integration protocol document, aligned with the current PR, as a shared reference for third-party editor extensions, language-server adapters, and other compatible tsserver hosts. The documentation links now point to this guide, with host-specific notes clearly separated from the common protocol. It also clarifies payload construction so that an invalid non-empty array cannot accidentally become a request to clear the overlay. When you have a chance, would you mind taking another look? I’d appreciate any further feedback. |
There was a problem hiding this comment.
ℹ️ No critical issues. One documentation precision suggestion inline.
Reviewed changes
Changes since the prior pullfrog review (19eed88), one docs-only commit:
- Added editor integration protocol guide — New
docs/editor-integration.mddefines the adapter contract for availability, payload shape, normalization, configuration state, lifecycle and replay rules, VS Code API v0 transport, and validation/release gates. - Documented VS Code configuration transport —
docs/tsserver-host.mdrecords verifiedApiV0.configurePlugin,PluginManagermap/replay, andprojectsUpdatedInBackgroundbehavior with source links. - Linked the guide from public docs — README, usage, and maintenance now point adapter authors to the new protocol guide.
big-pickle (free) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Changes since the prior pullfrog review (6a5a38f), one docs-only commit:
- Clarified tag parsing semantics —
docs/editor-integration.mdanddocs/usage.mdnow state that extra tags are matched exactly like project tags, that the built-in special names (css,keyframes,createGlobalStyle,globalCss,injectGlobal) keep their parsing behavior when enabled throughextraTags, and that only custom names such asglobalStyleget ordinary component parsing. This matchesgetTagKindinsrc/virtual-document/styled-virtual-document-provider.ts, which selects parsing by the matched tag's AST name regardless of source.
This addresses the single open documentation finding from the prior review; no behavioral changes.
big-pickle (free) | 𝕏
quantizor
left a comment
There was a problem hiding this comment.
Thanks for turning this around so fast. This is close and I'd like to merge it after one small cleanup pass.
Everything from my last review that changes behavior is in. I packed the current code (810d4af only adds docs on top of the build I tested), installed it in a scratch project and walked VS Code through configure, resend, invalid, mixed, clear, legacy and back with no typing at all. Squiggles update about 300ms after each real change and no-ops leave things alone. On main nothing updates until you edit, so the refreshDiagnostics ask is resolved. A plain stdio tsserver client with no VS Code code gets the same states, so the editor neutral wording holds. The new parsing note is right too. createGlobalStyle added through extraTags parses as a global stylesheet and globalStyle parses as a component.
Perf against main on outline/outline shows no change in typing, completion, geterr or heap. Resends are actually cheaper now because no-ops skip the cache flush. Heap stays flat over 1000 sends and 60 open and close cycles.
What I'd still like before merge:
- Drop the try/catch blocks around the config checks in editor-configuration.ts (hasOwn and parseEditorConfiguration) and the Object.keys block in isLegacyConfigurationOverride. That config is always parsed tsconfig JSON or a JSON configurePlugin payload, so it's a plain object and those reads can't throw. Keep the guard in the per-project apply loop since one bad project shouldn't leave the rest half updated, and keep the logging guards since the tsserver logger ends in fs.writeSync.
- Which host are the getCompilerOptions and refreshDiagnostics guards for? In TS 5.0 and 5.9 getCompilerOptions is a plain getter and refreshDiagnostics only calls the host's event handler, and the only tests that throw there use hand built fakes. If there's a real host that needs them, add a comment naming it. Otherwise I'd drop them.
- Replace the WeakRef listeners and registrations with a plain Set and remove a project when it closes. The registrations in tsserver-plugin.ts only feed a log line, so they can go.
- Move the legacy path changes in plugin-configuration.ts to their own PR. That's the whole normalize try/catch, getArrayLength, the defineProperty lint keys and cloneConfigurationRecord. They may be fine but they aren't part of this protocol.
- Remove the package-api tests that assert EDITOR_CONFIGURATION_PROTOCOL_VERSION is not exported. That name never shipped.
Small optional ones:
- refreshDiagnostics runs once per project, and in TS 5.9 each call already emits an event for every open file. So 5 projects means 5 events for one change. Calling it once per ProjectService would be enough. tsserver coalesces the work so the cost is only about 1ms today.
- "1 additional tags" in the applied log line.
|
Thanks for the careful review and for testing the packed build. The cleanup is in
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Changes since the previously reviewed commit 810d4af (one commit, 2aa5cf1):
- Dropped leftover config-read guards —
isLegacyConfigurationOverride,readProjectPluginConfiguration, andonConfigurationChangednow use the parsed configuration directly; the per-project apply loop and logging guards remain. - Replaced weak listeners with a strong
Set—addListenerwraps each project'sclose()once to remove all of that project's listeners and restore the original method, and the per-project registration registry intsserver-plugin.tsis gone. - Refreshed diagnostics once per
ProjectService—applyAndRefreshcallsrefreshDiagnostics()on one live project after all managers update, relying on tsserver's service-wide background update. - Moved legacy hardening out of scope —
plugin-configuration.tsis back tomainplus the two public editor types; copy-behavior tests and package-api assertions for the never-shipped protocol version constant are removed. - Updated docs, changeset, and lifecycle coverage — the docs and changeset match the close cleanup and per-service refresh, and new unit/e2e coverage exercises close/reopen and the single-refresh path.
Verification: targeted unit tests (78 passed), the full plugin-lifecycle e2e scenario (23 passed), and yarn compile all pass.
big-pickle (free) | 𝕏



Summary
This PR introduces an editor configuration protocol for appending tagged-template names at runtime. The protocol is intended for third-party editor extensions, language-server adapters, and other tsserver hosts across platforms, without tying the contract to VS Code or
vscode-styled-components.An integration that ships or loads this plugin can use the protocol to expose its own setting for compatible CSS tagged-template APIs and pass those tag names to tsserver. This can spare users from installing the package in each project, adding a plugin entry to every
tsconfig.json, or copying the full basetagslist for integration-specific tags. Project configuration remains available for project-specific choices, while integration defaults can stay with the integration.The implementation:
tags,validate,lint, andemmetsettings as the base, then appends normalized editor tags;ProjectService;configurePluginpayloads;@styled/typescript-styled-plugin/apiand documents the host contract.Motivation
Without an additive protocol, a third-party integration has two available paths: ask users to install and configure the plugin in each project, or send a normal
configurePluginpayload. The first places integration setup in each user'stsconfig.json. The second is not a good fit for an additive setting because tsserver treats the payload as the complete plugin configuration. For example,{ tags: ["globalStyle"] }replaces project-defined tags and resetsvalidate,lint, andemmetsettings.The new envelope provides a separate additive path that preserves the project's choices:
{ "editorConfiguration": { "extraTags": ["globalStyle", "styledNative"] } }Each valid payload replaces the previous overlay, and an empty
extraTagsarray clears it.Configuration behavior
editorConfigurationmarker is treated only as an editor envelope. If it is invalid, the current dynamic state remains unchanged.PluginCreateInfo.config; the project base is recovered fromcompilerOptions.pluginsbefore applying an overlay.globalStyleget ordinary component parsing, not global stylesheet parsing.Resilience
Editor envelopes and legacy runtime payloads are normalized before shared state changes. Malformed values are contained at the tsserver boundary, logs omit tag text, and dynamic state is scoped to one
ProjectService. Parsing and merging happen only when configuration changes, and repeated normalized payloads do not clear caches or refresh diagnostics.If the editor marker cannot be read safely, the payload follows legacy normalization rather than being classified as an invalid editor envelope. If a host throws while the plugin reads project compiler options, the plugin records the problem and falls back to the default project base so activation can continue.
Adapter documentation
The public adapter contract is documented in Editor integration protocol, aligned with this PR as a shared reference for third-party editor extensions, language-server adapters, and other compatible tsserver hosts. The README and usage guide link to it. Host-specific notes, including those for VS Code, describe individual integrations without limiting the scope of the protocol.
It covers payload validation, configuration state, replay, and adapter verification. The payload builder explicitly distinguishes invalid input from an empty array, so rejected entries cannot accidentally clear an existing overlay.
Compatibility
For users whose host does not send the new envelope, default, project, and legacy runtime configuration retain their existing behavior. The package root factory remains compatible with 1.0.1, and the public API adds only two protocol types. Real tsserver tests run against TypeScript 5.0, 5.9, and 6.0; TypeScript 4.9 retains the existing unsupported-host behavior. TypeScript 7 remains outside this tsserver-plugin protocol.
Testing
corepack yarn verifypasses, including formatting, lint, type checking, unit and scaling tests, default tsserver E2E tests, and package runtime, declaration, and contents checks. The TypeScript 5.0/5.9/6.0 tsserver matrix also passes.The documentation's payload builder passes strict TypeScript checking and 12 behavior cases checked against the plugin implementation, including explicit clearing and preservation of state for invalid input. Documentation links and anchors have also been checked.
The TypeScript 4.9 non-activation smoke test passes locally. The Node 14.21.3 smoke test remains a CI gate because the local arm64 host cannot execute the official darwin-x64 binary. Making the release workflow wait for those jobs and the full TypeScript matrix belongs to a separate workflow PR.
Release order
This is a backward-compatible minor capability. To make adoption predictable, host integrations can enable settings after a plugin release includes it, with the final integration artifact checked to confirm that it contains the updated plugin.