sygnal 6.1.0 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  All notable changes to Sygnal are listed here. Versions follow [semantic versioning](https://semver.org). Releases before 5.4.0 are described in the [GitHub releases](https://github.com/tpresley/sygnal/releases) and tags.
4
4
 
5
+ ## 6.1.1 — 2026-10-10
6
+
7
+ Editor tooling (PLAN-7). A language server, `sygnal-check lsp`, shows the checker's findings in the editor while you type: an intent selector that matches nothing, an action with no model entry, an EVENTS type nobody listens to, and the other wiring bugs that fail silently at runtime and that TypeScript can't see. It runs in VS Code (a new extension), Claude Code (a new plugin), Neovim, Helix, JetBrains IDEs (LSP4IJ) and any other LSP editor, all from the one server in `sygnal-check`, so the editor, the CLI, the Vite overlay and CI report the same findings from the same config file.
8
+
9
+ `sygnal` itself only changes in `sygnal/vite` (the `check` option reads the config file) and in the static diagnostic types: the core doesn't change (the size gate measures 42,690 B, as in 6.1.0).
10
+
11
+ **Measured impact.** On a generated 20,000-line project (`dev-plans/research/p7-spikes/0-A`, Apple M3 Max): a cold check takes about 230 ms (was about 400 ms) and a re-check after a one-file edit about 80 ms with the parse cache; in the editor an edit shows its findings about 260 ms later (150 ms of it the typing pause), and a hover answers in under 1 ms while a check runs. The server reports exactly what the CLI reports on every example app and fixture set (40 parity cases). Verified clients: VS Code 1.138, Claude Code 2.1.287, Neovim 0.12, Helix 25.07 and IntelliJ IDEA Community 2025.2 with LSP4IJ 0.21. Size: the kanban gate is unchanged, (a) 42,690 B with `nativeGlobalThis: false` (budget 42,700 B) and (b) 38,679 B by default. Whether in-editor findings help coding agents is measured after the release (PLAN-7 E-1).
12
+
13
+ **Companion packages.**
14
+ - **`sygnal-check` 0.4.0:** the language server (`sygnal-check lsp`), the config file, ranges, `related` locations and fixes on every finding, `sources` and a parse cache in the API, and a faster checker.
15
+ - **`create-sygnal-app` 2.2.0:** every template depends on `sygnal` ^6.1.1 and `sygnal-check` ^0.4.0, recommends the VS Code extension and carries `sygnal-check.config.json` (`strict: true`).
16
+ - **VS Code extension 0.1.0** (`sygnal.sygnal`, VS Code Marketplace and Open VSX) and **Claude Code plugin 0.1.0** (`/plugin marketplace add tpresley/sygnal`): new; see the [editor setup guide](https://sygnal.js.org/integration/editors/).
17
+
18
+ ### Added
19
+
20
+ - **`sygnal-check lsp`: a language server.** Stdio, part of `sygnal-check` (no new dependency). Findings for the whole project while you type, including unsaved buffers, with exact ranges and related locations; a hover with each code's explanation; quick fixes ("did you mean", the strict rewrites, "add a model entry"), "fix all" (what `--fix` does, on the unsaved text) and "suppress on this line". Checks run in a worker thread: on a 20,000-line project an edit shows its findings in about 260 ms (150 ms of it the typing pause) and a hover answers in under 1 ms during a check. Diagnostics are pushed, never pulled. Settings (`sygnal.strict`, `a11y`, `ignore`, `paths`, `verbose`, `trace.server`) override the config file per folder.
21
+ - **VS Code extension** (`editors/vscode/`, id `sygnal.sygnal`): runs the project's `sygnal-check` when it has the language server, else a bundled copy (always the bundled one in an untrusted workspace); commands Restart Server, Show Output, Explain Code…, Fix All in File; per-folder settings; snippets for a canonical component, an `EFFECT` entry and an `EVENTS` entry (JS and TS).
22
+ - **Claude Code plugin** (`editors/claude-code/`, marketplace `.claude-plugin/marketplace.json`: `/plugin marketplace add tpresley/sygnal`, `/plugin install sygnal@sygnal`): the `sygnal-dev` skill in one install, and the language server, so the agent sees findings after its edits. Without a capable `sygnal-check` in the project the server stays quiet.
23
+ - **Templates** (`create-sygnal-app`, all 10): `.vscode/extensions.json` recommends the extension, and `sygnal-check.config.json` sets `strict: true` (the Vike templates also `paths: ["pages"]`), so `npx sygnal-check`, the dev overlay and the editor check the same files the same way.
24
+ - **`sygnal-check` config file.** `sygnal-check.config.json`, or a `"sygnal-check"` key in `package.json`: `strict`, `a11y`, `ignore`, `paths`, `includeTests`. JSON only. It is found from the working directory up to the nearest project root. The CLI (`--config <file>`, `--no-config`), the MCP server, `check()` / `graph()` and the Vite plugin read it, so the editor, the dev overlay and CI agree. Flags and explicit options win over it; problems in it are SYG900 findings on the file, never a crash.
25
+ - **`sygnal/vite`: the `check` option defaults to the config file.** Explicit `check.*` options win, then the config file, then the `diagnostics.strict` / `diagnostics.ignore` defaults. Editing the config file re-checks. An older `sygnal-check` without config support gets exactly the options it got before. `plugin.d.ts` now also types `check.a11y`.
26
+ - **Ranges on findings.** Every `sygnal-check` diagnostic has `endLine` / `endColumn` (exclusive, UTF-16 columns, as editors count). Findings on multi-line elements underline the tag name; `sygnal-ignore` comments keep matching where they did. Pair and cross-file findings carry `related` locations (SYG104: the child element that renders the class; SYG105, SYG128, SYG129, SYG440, SYG609, SYG643).
27
+ - **Fixes as edits.** Diagnostics carry `fixes: [{ title, kind, preferred?, fixAll?, edits }]`: every unambiguous "did you mean" (SYG110, SYG112, SYG127, SYG140–142, SYG150, SYG151, SYG223, SYG226, SYG641, SYG707; SYG105 as a non-preferred rename), the `--fix` rewrites (SYG504, SYG505, SYG506, SYG612) with their import edits, "add a model entry" for SYG101 and "remove the unreachable entry" for SYG102. `--fix` applies the same `fixAll` edits an editor gets. `controlFixes()` returns the `--controls` conversions one selector at a time.
28
+ - **In-memory sources and a parse cache** for editors and watchers. `check()`, `checkFiles()`, `graph()`, `graphFiles()` and `buildProject()` take `sources` (unsaved buffers; new files resolve as imports) and `cache` (`createParseCache()`: unchanged files aren't parsed again). `fixFiles()` with `sources` fixes in memory and returns the texts and edits. The MCP server keeps a cache.
29
+ - **Types:** `InspectDiagnostic` gains the optional `endLine`, `endColumn`, `related` and `fixes` (`InspectRelatedLocation`, `InspectFix`, `InspectTextEdit`).
30
+
31
+ ### Changed
32
+
33
+ - SYG900's explanation also covers config-file findings.
34
+ - `sygnal-check` performance: walks over a whole file use a flat node list built once per parse. A 20,000-line project checks in about 230 ms cold (was about 400 ms) and re-checks after a one-file edit in about 80 ms with a cache (was about 350 ms without one).
35
+
36
+ ### Fixed
37
+
38
+ - `sygnal-check`: three module-level caches keyed by AST nodes (behavior definitions, widgets, a11y `describe()`) could serve one build's data to the next when ASTs are reused; now keyed per project.
39
+
5
40
  ## 6.1.0 — 2026-10-10
6
41
 
7
42
  A new subpath, `sygnal/ai`, covers the two AI jobs Sygnal apps now meet: calling language models from the app, and letting agents operate it. A chat request is a driver request, like HTTP: the reply streams into a `delta` action and arrives whole as `ok`, through the same reply actions as `makeFetchDriver`, with transports for local models (Ollama through `openResponses()`), an AI SDK route on your server (`uiMessageStream()`), Anthropic, OpenAI-compatible APIs, AG-UI and Chrome's built-in model. `decide()` asks a decision model typed questions through the fetch driver. One declaration per component, the `agent` static, says what an LLM may read and do in the app; the same declaration becomes the tools of an in-app assistant (the `chat` behavior), a command bar (`commandBar`), browser agents through WebMCP (`experimentalExposeWebMcp`), an MCP Apps host (`makeMcpAppDriver`), and coding agents through the dev server (`sygnal({ mcp: true })`). Tests answer chat requests and call agent tools without a model.
@@ -666,10 +666,19 @@ if (import.meta.hot) installMcpBridge(import.meta.hot, { agentTools, confirm: ${
666
666
  * `diagnostics: { mode, ignore }` unless the call sets `diagnostics`
667
667
  * itself. With the default ('warn', no ignore list) there is no wrapper.
668
668
  * 5. `check` option: runs sygnal-check (an optional dependency, loaded
669
- * lazily from the project; `strict` defaults to `diagnostics.strict`)
670
- * over `include` (default: the existing ones of src/, pages/ and
671
- * renderer/, else the project root, with a notice) when the dev
672
- * server starts and again after every source file change. Results go to
669
+ * lazily from the project) over `include` when the dev server starts and
670
+ * again after every source file change. Settings, highest first: the
671
+ * explicit `check.*` options; the project's sygnal-check config file
672
+ * (sygnal-check.config.json or the package.json "sygnal-check" key, read
673
+ * by sygnal-check itself, PLAN-7 C-5 / D299; JSON only, so the editor and
674
+ * the CLI see the same settings without evaluating vite.config); then
675
+ * `strict` from `diagnostics.strict`, `ignore` from `diagnostics.ignore`,
676
+ * and `include` from the existing ones of src/, pages/ and renderer/,
677
+ * else the project root (with a notice). An older sygnal-check without
678
+ * config support (no `loadConfig` export) gets exactly the options it got
679
+ * before: the explicit ones over the `diagnostics` defaults. With config
680
+ * support a change to the config file also re-checks (`include` is fixed
681
+ * when the server starts). Results go to
673
682
  * the terminal in sygnal-check's format, and to the browser as a
674
683
  * 'sygnal:check' HMR event that the dev client ('virtual:sygnal/dev')
675
684
  * logs with console.warn (non-disruptive). The dev client asks for the
@@ -1309,6 +1318,8 @@ async function loadSygnalCheck(root) {
1309
1318
  }
1310
1319
  }
1311
1320
  const SOURCE_RE = /\.[cm]?[jt]sx?$/;
1321
+ // sygnal-check's config sources (PLAN-7 C-5): a change re-checks
1322
+ const CONFIG_FILES = ['sygnal-check.config.json', 'package.json'];
1312
1323
  /** sygnal-check's one-line format: `file:line:col CODE [severity] Component: message (fix)` */
1313
1324
  function formatLine(d) {
1314
1325
  const where = d.file ? `${d.file}:${d.line}:${d.column}` : '<sygnal-check>';
@@ -1325,11 +1336,11 @@ const DEFAULT_INCLUDE = ['src', 'pages', 'renderer'];
1325
1336
  * directory, or when none of the given paths exists, so an empty check is
1326
1337
  * never a silent all-clear.
1327
1338
  */
1328
- function checkInclude(root, include, notice) {
1339
+ function checkInclude(root, include, notice, label = 'check.include') {
1329
1340
  const exists = (p) => /[*?[{]/.test(p) || fs.existsSync(path.resolve(root, p));
1330
1341
  if (include && include.length) {
1331
1342
  if (!include.some(exists))
1332
- notice(`[sygnal] sygnal-check: none of check.include (${include.join(', ')}) exists under ${root}, so nothing is checked`);
1343
+ notice(`[sygnal] sygnal-check: none of ${label} (${include.map(p => path.isAbsolute(p) ? path.relative(root, p) || '.' : p).join(', ')}) exists under ${root}, so nothing is checked`);
1333
1344
  return include;
1334
1345
  }
1335
1346
  const dirs = DEFAULT_INCLUDE.filter(exists);
@@ -1347,11 +1358,43 @@ async function startChecker(server, root, opts, defaults, explicit) {
1347
1358
  logger.info('[sygnal] sygnal-check is not installed, so static checks are off (npm i -D sygnal-check)');
1348
1359
  return;
1349
1360
  }
1350
- const include = checkInclude(root, opts.include, (m) => logger.info(m));
1351
- const ignore = opts.ignore || defaults.ignore;
1352
- const strict = opts.strict === undefined ? defaults.strict : !!opts.strict;
1353
- // D144: the a11y lane is a warning unless asked for as an error, under strict too
1354
- const a11y = opts.a11y === 'error' ? 'error' : 'warn';
1361
+ const notice = (m) => logger.info(m);
1362
+ // PLAN-7 C-5 (D299): a sygnal-check with config support resolves the settings itself:
1363
+ // explicit check.* options > its config file > the `diagnostics` defaults passed as `defaults`.
1364
+ // An older one gets exactly what it got before.
1365
+ const configAware = typeof mod.loadConfig === 'function';
1366
+ let include;
1367
+ let checkOptions;
1368
+ if (configAware) {
1369
+ let configPaths;
1370
+ if (!(opts.include && opts.include.length)) {
1371
+ try {
1372
+ configPaths = mod.loadConfig(root)?.config?.paths;
1373
+ }
1374
+ catch (_) { }
1375
+ }
1376
+ include = configPaths && configPaths.length
1377
+ ? checkInclude(root, configPaths, notice, "the config file's paths")
1378
+ : checkInclude(root, opts.include, notice);
1379
+ checkOptions = {
1380
+ cwd: root,
1381
+ strict: opts.strict === undefined ? undefined : !!opts.strict,
1382
+ ignore: opts.ignore,
1383
+ a11y: opts.a11y === 'error' || opts.a11y === 'warn' ? opts.a11y : undefined,
1384
+ defaults: { strict: defaults.strict, ignore: defaults.ignore },
1385
+ };
1386
+ }
1387
+ else {
1388
+ include = checkInclude(root, opts.include, notice);
1389
+ // D144: the a11y lane is a warning unless asked for as an error, under strict too
1390
+ checkOptions = {
1391
+ cwd: root,
1392
+ strict: opts.strict === undefined ? defaults.strict : !!opts.strict,
1393
+ ignore: opts.ignore || defaults.ignore,
1394
+ a11y: opts.a11y === 'error' ? 'error' : 'warn',
1395
+ };
1396
+ }
1397
+ const a11y = checkOptions.a11y;
1355
1398
  // Only error-severity findings open Vite's overlay: while an overlay is open
1356
1399
  // Vite's client reloads the page on the first HMR update, so warnings stay
1357
1400
  // in the terminal and the browser console. overlay: 'warn' is treated as
@@ -1382,14 +1425,15 @@ async function startChecker(server, root, opts, defaults, explicit) {
1382
1425
  const run = (initial = false) => {
1383
1426
  let diags;
1384
1427
  try {
1385
- diags = mod.check(include, { cwd: root, strict, ignore, a11y });
1428
+ diags = mod.check(include, checkOptions);
1386
1429
  }
1387
1430
  catch (err) {
1388
1431
  logger.warn(`[sygnal] sygnal-check failed: ${err?.message || err}`, { timestamp: true });
1389
1432
  return;
1390
1433
  }
1391
1434
  // A sygnal-check from before D144 reports SYG7xx as errors under strict: keep them warnings
1392
- if (a11y !== 'error') {
1435
+ // (one with config support postdates D144 and resolves a11y itself)
1436
+ if (!configAware && a11y !== 'error') {
1393
1437
  diags = diags.map(d => d && d.severity === 'error' && /^SYG7\d\d$/.test(d.code) ? { ...d, severity: 'warn' } : d);
1394
1438
  }
1395
1439
  const shown = diags.filter(d => d.severity !== 'info');
@@ -1443,7 +1487,9 @@ async function startChecker(server, root, opts, defaults, explicit) {
1443
1487
  catch (_) { }
1444
1488
  let timer;
1445
1489
  const onChange = (file) => {
1446
- if (!SOURCE_RE.test(file) || file.split(/[\\/]/).includes('node_modules'))
1490
+ if (file.split(/[\\/]/).includes('node_modules'))
1491
+ return;
1492
+ if (!SOURCE_RE.test(file) && !(configAware && CONFIG_FILES.includes(path.basename(file))))
1447
1493
  return;
1448
1494
  clearTimeout(timer);
1449
1495
  timer = setTimeout(run, 100);