sygnal 6.3.0 → 6.4.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.
@@ -249,6 +249,9 @@ const CODE_TITLES = {
249
249
  SYG425: 'isolatedState slice lacks initialState keys',
250
250
  // PLAN-9 (D471): checks/next.ts; dev entry and sygnal-check
251
251
  SYG426: 'Deprecated lowercase marker tag',
252
+ // checks/next.ts and sygnal-check: a component reference that is not a function (an import of
253
+ // a name the module doesn't export is undefined under Vitest)
254
+ SYG427: 'CHILD.select() of something that is not a component',
252
255
  // PLAN-9 (D470): checks/next.ts; dev entry
253
256
  SYG453: 'Two children with the same identity share one instance',
254
257
  SYG601: "Retired in 6.0: Missing or invalid sources",
@@ -455,6 +458,8 @@ const DEV_CODE_SEVERITY = {
455
458
  SYG425: 'warn',
456
459
  // PLAN-9 (checks/next.ts): D471 a lowercase <collection> / <switchable> tag (deprecated in 6.3.0)
457
460
  SYG426: 'warn',
461
+ // checks/next.ts: CHILD.select(undefined), usually a missing export
462
+ SYG427: 'error',
458
463
  // PLAN-9 (checks/next.ts): D470 key identity, two children with one identity
459
464
  SYG453: 'warn',
460
465
  SYG612: 'error',
@@ -771,11 +776,20 @@ const underTest = (c) => {
771
776
  const dom = c?.sources?.[c.DOMSourceName || 'DOM'];
772
777
  return !!dom && typeof dom == 'object' && '_hub' in dom;
773
778
  };
774
- // G-620: an LLM / chat sink gets the chat driver's fix, not makeFetchDriver's
775
- const driverFix = (sink) => `Pass a driver named ${sink} to run(): ` + (/^(LLM|CHAT|AI|ASSISTANT)$/.test(sink)
776
- ? `run(App, { ${sink}: makeChatDriver({ transport: openResponses({ baseURL: 'http://localhost:11434/v1', model: 'llama3.2' }) }) }) (from 'sygnal/ai'; uiMessageStream('/api/chat') for an AI SDK server)`
777
- : `run(App, { ${sink}: makeFetchDriver() }) for HTTP, or driverFromAsync(fn) / your own driver`) +
778
- `. Check the spelling against the drivers you pass`;
779
+ // G-620: the usual driver for a known sink name (an LLM / chat sink gets the chat driver's fix,
780
+ // SW the service worker's), the generic text for any other name
781
+ const DRIVER_FOR = [
782
+ [/^(LLM|CHAT|AI|ASSISTANT)$/, (s) => `run(App, { ${s}: makeChatDriver({ transport: openResponses({ baseURL: 'http://localhost:11434/v1', model: 'llama3.2' }) }) }) (from 'sygnal/ai'; uiMessageStream('/api/chat') for an AI SDK server)`],
783
+ [/^(SW|SERVICE_WORKER)$/, (s) => `run(App, { ${s}: makeServiceWorkerDriver('/sw.js', { enabled: import.meta.env.PROD }) }) (enabled: false keeps the driver, with no service worker, on a dev server)`],
784
+ [/^(HTTP|API|FETCH)$/, (s) => `run(App, { ${s}: makeFetchDriver() })`],
785
+ [/^(WS|SOCKET)$/, (s) => `run(App, { ${s}: makeSocketDriver() })`],
786
+ ];
787
+ const driverFix = (sink) => {
788
+ const known = DRIVER_FOR.find(([re]) => re.test(sink));
789
+ return `Pass a driver named ${sink} to run(): ` +
790
+ (known ? known[1](sink) : `run(App, { ${sink}: makeFetchDriver() }) for HTTP, or driverFromAsync(fn) / your own driver`) +
791
+ `. Check the spelling against the drivers you pass`;
792
+ };
779
793
  const wiringCheck = {
780
794
  id: 'wiring',
781
795
  onIntent(component, actionNames) {
@@ -1324,6 +1338,10 @@ onReset(() => {
1324
1338
  });
1325
1339
  const unref = (t) => { if (t && typeof t.unref === 'function')
1326
1340
  t.unref(); return t; };
1341
+ /**
1342
+ * The view's source, plus the words sygnal/vite registered for the component (its module's and
1343
+ * its relative imports' string literals: markup a view helper draws), when it did
1344
+ */
1327
1345
  function viewSource(component) {
1328
1346
  const view = component && component.view;
1329
1347
  if (typeof view !== 'function')
@@ -1336,6 +1354,9 @@ function viewSource(component) {
1336
1354
  catch (_) {
1337
1355
  src = '';
1338
1356
  }
1357
+ const registered = globalThis.__SYGNAL_VIEW_TOKENS__?.get?.(view);
1358
+ if (typeof registered == 'string')
1359
+ src += '\n' + registered;
1339
1360
  viewTokens.set(view, src);
1340
1361
  }
1341
1362
  return src;
@@ -2980,7 +3001,8 @@ const persistCheck = {
2980
3001
  const o = p.options, init = view.initialState;
2981
3002
  if (!o || !init || typeof init != 'object')
2982
3003
  return;
2983
- const keys = Object.keys(init);
3004
+ // a behavior's slice (`uses = { chat: chat() }`) is a state key too
3005
+ const keys = [...Object.keys(init), ...Object.keys(view.uses || {})];
2984
3006
  for (const opt of ['pick', 'omit']) {
2985
3007
  for (const k of Array.isArray(o[opt]) ? o[opt] : []) {
2986
3008
  if (keys.includes(k) || !once(`SYG223:${name}:${opt}:${k}`))
@@ -3694,6 +3716,18 @@ function removed(component, name, anchor, detail) {
3694
3716
  data: { form: anchor, guide: `${MIGRATE}#${anchor}`, ...(detail ? { detail } : {}) },
3695
3717
  });
3696
3718
  }
3719
+ /** SYG427: CHILD.select(x) with no component (an import of a name the module doesn't export) */
3720
+ function notComponent(f, x) {
3721
+ if (!once(`SYG427:${f.name}`))
3722
+ return;
3723
+ const got = x === null ? 'null' : typeof x == 'object' ? 'an object' : String(x);
3724
+ devReport('SYG427', {
3725
+ component: f.name,
3726
+ message: `${f.name}'s intent calls CHILD.select(${got}): that is not a component, so it never emits`,
3727
+ fix: "Pass the child component itself, and check its import: a name its module doesn't export is undefined (Vitest runs on; the browser fails to link the module). Export it: export function Child() { … }",
3728
+ data: { got },
3729
+ });
3730
+ }
3697
3731
  /** the removed forms a definition shows (statics, view arity, model keys), once per function */
3698
3732
  let seenDefs = new WeakSet();
3699
3733
  // G-332: resetChecks() forgets them (as the once() dedupe)
@@ -4077,8 +4111,13 @@ function nextHooks(_api) {
4077
4111
  const v = t[k];
4078
4112
  if (k !== 'CHILD' || !v || typeof v.select != 'function')
4079
4113
  return v;
4080
- return { ...v, select: (x, ...r) => { if (typeof x == 'string')
4081
- removed(f, f.name, 'child-select-name', `CHILD.select('${x}')`); return v.select(x, ...r); } };
4114
+ return { ...v, select: (x, ...r) => {
4115
+ if (typeof x == 'string')
4116
+ removed(f, f.name, 'child-select-name', `CHILD.select('${x}')`);
4117
+ else if (typeof x != 'function')
4118
+ notComponent(f, x);
4119
+ return v.select(x, ...r);
4120
+ } };
4082
4121
  },
4083
4122
  });
4084
4123
  },