sygnal 6.3.0 → 6.5.0

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/astro/index.cjs.js +69 -3
  3. package/dist/astro/index.cjs.js.map +1 -1
  4. package/dist/astro/index.mjs +69 -3
  5. package/dist/astro/index.mjs.map +1 -1
  6. package/dist/astro/server.cjs.js +3 -2
  7. package/dist/astro/server.cjs.js.map +1 -1
  8. package/dist/astro/server.mjs +3 -2
  9. package/dist/astro/server.mjs.map +1 -1
  10. package/dist/diagnostics.cjs.js +105 -19
  11. package/dist/diagnostics.cjs.js.map +1 -1
  12. package/dist/diagnostics.esm.js +105 -19
  13. package/dist/diagnostics.esm.js.map +1 -1
  14. package/dist/guide/mcp-apps.md +59 -4
  15. package/dist/index.cjs.js +54 -25
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +27 -6
  18. package/dist/index.esm.js +54 -25
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/sygnal.min.js +1 -1
  21. package/dist/sygnal.min.js.map +1 -1
  22. package/dist/vike/config/package.json +1 -1
  23. package/dist/vite/plugin.cjs.js +69 -3
  24. package/dist/vite/plugin.cjs.js.map +1 -1
  25. package/dist/vite/plugin.mjs +69 -3
  26. package/dist/vite/plugin.mjs.map +1 -1
  27. package/llms.txt +4 -4
  28. package/package.json +1 -1
  29. package/src/core/cell.ts +25 -7
  30. package/src/core/hosts/collection.ts +6 -11
  31. package/src/core/hosts/switchable.ts +2 -2
  32. package/src/core/instance.ts +2 -2
  33. package/src/extra/diagnostics/checks/collections.ts +31 -25
  34. package/src/extra/diagnostics/checks/dom.ts +10 -2
  35. package/src/extra/diagnostics/checks/next.ts +39 -1
  36. package/src/extra/diagnostics/checks/persist.ts +4 -2
  37. package/src/extra/diagnostics/checks/wiring.ts +14 -6
  38. package/src/extra/diagnostics/codes.ts +9 -0
  39. package/src/extra/pwa.ts +3 -1
  40. package/src/extra/reducers.ts +10 -4
  41. package/src/index.d.ts +26 -5
  42. package/src/vite/plugin.ts +63 -2
@@ -117,6 +117,12 @@
117
117
  * /__sygnal/mcp (./mcp.ts: streamable HTTP, loopback / local Host / local Origin only)
118
118
  * and the dev entries also import 'virtual:sygnal/mcp', which connects the page over the
119
119
  * HMR channel (installMcpBridge from 'sygnal/devtools'). Never in `vite build` or Vitest.
120
+ * 11. SYG103 view words (dev server with diagnostics on; never `vite build`, SSR or Vitest): a
121
+ * module that sets `X.intent` or `X.uses` gets a tail registering, for each such X, the
122
+ * words of its string literals and of the relative modules it imports, in
123
+ * `globalThis.__SYGNAL_VIEW_TOKENS__`. The runtime SYG103 check then takes a selector drawn
124
+ * by a view helper (`{state.open && panel()}`) as conditionally rendered, as it does for
125
+ * markup in the view function itself (see viewTokensTail).
120
126
  *
121
127
  * Why not Vite's `define`? Vite's dependency optimizer does not apply user
122
128
  * `define` replacements to pre-bundled dependencies (only process.env.NODE_ENV),
@@ -483,8 +489,17 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
483
489
  transform(this: any, code: string, id: string, opts?: { ssr?: boolean }) {
484
490
  // SYG666 (PLAN-5 W-2): an adapter entry imported without its optional peers; every mode
485
491
  const adapters = adapterImports(this, code, id)
486
- if (adapters) return checkPeers(this, adapters, id).then(() => devTransform.call(this, code, id, opts))
487
- return devTransform.call(this, code, id, opts)
492
+ const result = adapters
493
+ ? checkPeers(this, adapters, id).then(() => devTransform.call(this, code, id, opts))
494
+ : devTransform.call(this, code, id, opts)
495
+ // 11. the words SYG103 may take as conditionally rendered markup (dev server, diagnostics on)
496
+ const tokens = isServe && !isVitest && devOn && !opts?.ssr ? viewTokensTail(this, code, id) : null
497
+ if (!tokens) return result
498
+ return Promise.all([result, tokens]).then(([r, tail]: [any, string]) => {
499
+ if (!tail) return r
500
+ if (r == null) return withSourcemap(code, [], tail, id)
501
+ return typeof r == 'string' ? r + tail : { ...r, code: r.code + tail }
502
+ })
488
503
  },
489
504
  }
490
505
 
@@ -572,6 +587,52 @@ export default function sygnal(options: SygnalPluginOptions = {}) {
572
587
  }
573
588
  }
574
589
 
590
+ // 11. SYG103 (the dev runtime check of an intent selector that never matched) lets a selector
591
+ // through as conditionally rendered when its words appear in the component's view; the runtime
592
+ // can read only the view function's own source. A component module (one that sets `X.intent` or
593
+ // `X.uses`) gets, in dev, a tail registering for each such component the words of its string
594
+ // literals and those of the relative modules it imports, where view helpers live
595
+ // (globalThis.__SYGNAL_VIEW_TOKENS__, a WeakMap read by checks/dom.ts).
596
+ const COMPONENT_WITH_SELECTORS_RE = /(?:^|[^\w$.])([A-Z][\w$]*)\.(?:intent|uses)\s*=(?!=)/g
597
+ const STRING_RE = /(['"])((?:\\.|(?!\1)[^\\\n])*)\1|`((?:\\[\s\S]|[^\\`])*)`/g
598
+ const RELATIVE_IMPORT_RE = /(?:\bfrom|\bimport)\s*\(?\s*(['"])(\.{1,2}\/[^'"]+)\1/g
599
+
600
+ // A string that is itself a selector ('.save', '#id', 'form.ask', '[data-x]') or a path / URL: its
601
+ // words aren't markup (the intent's own DOM.click('.svae') would otherwise vouch for its typo)
602
+ const NOT_MARKUP_RE = /(?:^|[\s>+~,(])[\w-]*[.#[][\w-]|^\s*(?:\/|[\w+-]+:\/\/)/
603
+
604
+ /** the class / id / tag-like words in a module's string and template literals (comments skipped) */
605
+ function stringWords(code: string, into: Set<string>): void {
606
+ for (const m of blankOut(code, false).matchAll(STRING_RE)) {
607
+ const text = m[2] ?? m[3] ?? ''
608
+ if (NOT_MARKUP_RE.test(text)) continue
609
+ for (const w of text.match(/[A-Za-z_][\w-]*/g) || []) into.add(w)
610
+ }
611
+ }
612
+
613
+ function viewTokensTail(ctx: any, code: string, id: string): Promise<string> | null {
614
+ const file = cleanId(id)
615
+ if (!/\.[jt]sx?$/.test(file) || id.includes('node_modules') || id.startsWith('\0')) return null
616
+ const names = new Set<string>()
617
+ for (const m of blankOut(code, true).matchAll(COMPONENT_WITH_SELECTORS_RE)) names.add(m[1])
618
+ if (!names.size) return null
619
+ const words = new Set<string>()
620
+ stringWords(code, words)
621
+ const specs = new Set(Array.from(blankOut(code, false).matchAll(RELATIVE_IMPORT_RE), m => m[2]))
622
+ return Promise.all([...specs].map(async (spec) => {
623
+ try {
624
+ const r = await ctx.resolve(spec, id)
625
+ const dep = r && !r.external && cleanId(r.id)
626
+ if (!dep || !/\.[cm]?[jt]sx?$/.test(dep) || dep.includes('node_modules')) return
627
+ ctx.addWatchFile?.(dep)
628
+ stringWords(await fs.promises.readFile(dep, 'utf8'), words)
629
+ } catch (_) { /* unreadable: its words are just not added */ }
630
+ })).then(() => {
631
+ const comps = [...names].map(n => `typeof ${n} == 'function' && ${n}`).join(', ')
632
+ return `\n;(() => { try { const m = globalThis.__SYGNAL_VIEW_TOKENS__ ||= new WeakMap(), t = ${JSON.stringify([...words].join(' '))}; for (const c of [${comps}]) if (c) m.set(c, t) } catch (_) {} })();\n`
633
+ })
634
+ }
635
+
575
636
  // PLAN-5 W-2 (D209): the adapter entries and the optional peer dependencies each one imports
576
637
  // (the Zag packages pinned together: they depend on each other's exact versions)
577
638
  const ZAG = '~1.45.0'