Rendered from specs/S03-performance.md — edit it there, not here.
S03 — Performance and Resource Management
Overview
The extension interacts with long-running processes (Python subprocess, remote HTTP fetches) and creates persistent webview panels. These requirements govern timeouts, cleanup, and async behaviour.
Requirements
Python Subprocess
- S03-SR-01 The
json-schema-for-humansinvocation MUST be asynchronous; it MUST NOT block the VS Code extension host event loop. - S03-SR-02 The subprocess MUST have a configurable timeout; if it exceeds the timeout the process MUST be killed and an error page MUST be shown.
Remote Fetch
- S03-SR-03 All outbound HTTP requests for remote schemas MUST carry an explicit timeout (via
AbortSignalor equivalent) to prevent the UI from hanging on unreachable endpoints.
Webview Lifecycle
- S03-SR-04 When a preview panel is disposed the extension MUST remove it from the
openJsonSchemaFilesmap and delete its entry fromrawOutputCache. - S03-SR-05 The extension MUST track all created webview panels in
context.subscriptions(or equivalent) so they are disposed when the extension deactivates. - S03-SR-06
deactivate()MUST calldisposeAllPanels()to release all open webview panels.
Schema Discovery
- S03-SR-07 Workspace schema file discovery (for the binding Quick Pick) MUST be performed asynchronously using
vscode.workspace.findFilesso it does not block the UI while scanning large workspaces.
Debounce Timer Cleanup
- S03-SR-08 Live-update debounce timers MUST be cleared when the associated preview panel is disposed to prevent stale Python invocations after the panel is closed.
Logging
- S03-SR-09 The extension MUST create a dedicated
LogOutputChannelnamed"JSON Schema Preview"and route all diagnostic output to it. - S03-SR-10 The extension MUST NOT call
console.log/console.errorin production code; all logging MUST go through theLogOutputChannel.
Performance Budgets
Concrete budgets decided 2026-07-04. The debounce delay budget lives in F02-FR-04 (default 1500 ms, minimum 500 ms) and is not restated here.
- S03-SR-11 The
json-schema-for-humansrender subprocess MUST default to a 30 000 ms timeout; auxiliary CLI captures (e.g. interpreter/version probes) MUST default to 10 000 ms. - S03-SR-12 Remote schema HTTP fetches MUST default to a 30 000 ms timeout.
- S03-SR-13 Preview generation SHOULD complete within 2 s (p95) for schema files up to 500 KB on typical developer hardware. Slower renders are not an error, but SHOULD be treated as performance regressions and investigated.
- S03-SR-14 The render-subprocess and remote-fetch timeouts MUST be user-configurable via
jsonschema.preview.renderTimeoutandjsonschema.remoteFetchTimeoutrespectively. A configured value that is missing, non-numeric, or non-positive MUST fall back to the SR-11/SR-12 default; any valid value MUST be clamped to a minimum of 1000 ms so a mistyped tiny value cannot make every render fail instantly.
Build Size Budgets
A large or steadily-growing bundle slows down every user's install and extension-host activation, regardless of the runtime timeouts above. Budgets decided 2026-07-17, sized with roughly 50-60% headroom over the measured baseline at the time (production dist/extension.js ~1.91 MB; packaged .vsix ~748 KB after the S03-SR-17 packaging-hygiene fix) so routine dependency growth doesn't trip the gate, while still catching an accidental large dependency or an un-excluded dev-only tree shipped into the package.
- S03-SR-15 A script MUST measure, on every CI build, (a) the production webpack bundle (
dist/extension.js, built vianpm run package) and (b) the packaged.vsixproduced byvsce package --no-dependencies, and MUST fail (non-zero exit) if either exceeds its budget in S03-SR-16. - S03-SR-16 Budgets:
dist/extension.jsMUST NOT exceed 3 MB; the packaged.vsixMUST NOT exceed 1.5 MB. A run that exceeds 85% of either budget MUST print a warning (non-fatal) so creeping growth is visible in CI logs before it becomes a hard failure. - S03-SR-17
.vscodeignoreMUST exclude every file/directory that is not needed at extension runtime (specs, spec-kit tooling, git hooks, alternate tsconfigs, lint/test/release config, project meta-docs) so the packaging step in S03-SR-15 measures — and ships — only what the extension host actually loads. - S03-SR-18 The measurement in S03-SR-15 MUST be written to a committed, version-controlled snapshot file (
bundle-size.json, at the repo root, excluded from the.vsixitself per S03-SR-17) each time it runs, so the size trend over time is inspectable via that file's git history — the same patternmaturity-score.jsonuses for the maturity score. A scheduled workflow MUST refresh this snapshot regularly (independent of whether any PR happened to trip the budget) so the tracked history doesn't go stale between size-relevant changes.
Acceptance Criteria
- Closing the preview panel while a live-update is pending produces no error and no orphaned Python process.
- Opening the Output panel and selecting "JSON Schema Preview" shows structured log output from the extension.
- With no configuration override, a hung Python render or remote fetch is killed/aborted after 30 s and an error page is shown.
npm run check:bundle-size(run afternpm run package) exits non-zero and names the offending artifact when either the production bundle or the packaged.vsixexceeds its S03-SR-16 budget.bundle-size.json'sgit log -pshows one entry per refresh, so the size trend over releases is readable without re-running the build.
Requirement traceability
Status of each requirement in specs/traceability.json. Test coverage is auto-discovered from [ID] tags in test titles and is not listed here.
| Requirement | Status | Implementation | Note |
|---|---|---|---|
S03-SR-01 | manual | src/python.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-02 | manual | src/python.ts, src/settings.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-03 | implemented | src/SchemaAuthManager.ts | unit-tested via the vscode mock (2026-07 coverage expansion) |
S03-SR-04 | manual | src/PreviewWebPanel.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-05 | manual | src/PreviewWebPanel.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-06 | manual | src/PreviewWebPanel.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-07 | manual | src/SchemaBindingManager.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-08 | manual | src/PreviewWebPanel.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-09 | implemented | src/extension.ts | createOutputChannel("JSON Schema Preview", {log:true}) |
S03-SR-10 | manual | src/extension.ts | VS Code API-bound; verified by manual / E2E testing |
S03-SR-11 | manual | src/python.ts, src/settings.ts | render 30s default in settings.ts + capture 10s in python.ts; render clamp/read unit-tested |
S03-SR-12 | implemented | src/settings.ts | remote fetch 30s default, resolved + read unit-tested |
S03-SR-13 | implemented | src/PreviewWebPanel.ts | measured in E2E (src/test/integration/suites/singleFolder.previewPerf.test.ts): p95 over 20 runs of the fixture schema against the built-in renderer stays well within the 2s budget; see S08-SR-07 for the same measurement |
S03-SR-14 | implemented | src/settings.ts | resolveTimeoutMs clamp/fallback + config readers unit-tested; wired into PreviewWebPanel/ValidationManager/SchemaCache |
S03-SR-15 | implemented | scripts/check-bundle-size.mjs, .github/workflows/ci.yml | ci.yml build job runs `npm run package && npm run check:bundle-size` on every build; script exits non-zero on budget overrun |
S03-SR-16 | implemented | scripts/check-bundle-size.mjs | BUNDLE_BUDGET_BYTES=3MB / VSIX_BUDGET_BYTES=1.5MB constants; WARN_RATIO=0.85 prints a non-fatal warning before the hard fail |
S03-SR-17 | implemented | .vscodeignore | excludes specs/, .specify/, .husky/, alternate tsconfigs, lint/test/release config, project meta-docs; reduced the packaged .vsix from 864KB/83 files to ~748KB/13 files |
S03-SR-18 | implemented | scripts/check-bundle-size.mjs, bundle-size.json, .github/workflows/maturity-refresh.yml | check-bundle-size.mjs writes bundle-size.json on every run; the weekly refresh workflow commits it (alongside the maturity score) when it changes |