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.
@@ -269,6 +269,9 @@ const CODE_TITLES = {
269
269
  SYG425: 'isolatedState slice lacks initialState keys',
270
270
  // PLAN-9 (D471): checks/next.ts; dev entry and sygnal-check
271
271
  SYG426: 'Deprecated lowercase marker tag',
272
+ // checks/next.ts and sygnal-check: a component reference that is not a function (an import of
273
+ // a name the module doesn't export is undefined under Vitest)
274
+ SYG427: 'CHILD.select() of something that is not a component',
272
275
  // PLAN-9 (D470): checks/next.ts; dev entry
273
276
  SYG453: 'Two children with the same identity share one instance',
274
277
  SYG601: "Retired in 6.0: Missing or invalid sources",
@@ -475,6 +478,8 @@ const DEV_CODE_SEVERITY = {
475
478
  SYG425: 'warn',
476
479
  // PLAN-9 (checks/next.ts): D471 a lowercase <collection> / <switchable> tag (deprecated in 6.3.0)
477
480
  SYG426: 'warn',
481
+ // checks/next.ts: CHILD.select(undefined), usually a missing export
482
+ SYG427: 'error',
478
483
  // PLAN-9 (checks/next.ts): D470 key identity, two children with one identity
479
484
  SYG453: 'warn',
480
485
  SYG612: 'error',
@@ -791,11 +796,20 @@ const underTest = (c) => {
791
796
  const dom = c?.sources?.[c.DOMSourceName || 'DOM'];
792
797
  return !!dom && typeof dom == 'object' && '_hub' in dom;
793
798
  };
794
- // G-620: an LLM / chat sink gets the chat driver's fix, not makeFetchDriver's
795
- const driverFix = (sink) => `Pass a driver named ${sink} to run(): ` + (/^(LLM|CHAT|AI|ASSISTANT)$/.test(sink)
796
- ? `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)`
797
- : `run(App, { ${sink}: makeFetchDriver() }) for HTTP, or driverFromAsync(fn) / your own driver`) +
798
- `. Check the spelling against the drivers you pass`;
799
+ // G-620: the usual driver for a known sink name (an LLM / chat sink gets the chat driver's fix,
800
+ // SW the service worker's), the generic text for any other name
801
+ const DRIVER_FOR = [
802
+ [/^(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)`],
803
+ [/^(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)`],
804
+ [/^(HTTP|API|FETCH)$/, (s) => `run(App, { ${s}: makeFetchDriver() })`],
805
+ [/^(WS|SOCKET)$/, (s) => `run(App, { ${s}: makeSocketDriver() })`],
806
+ ];
807
+ const driverFix = (sink) => {
808
+ const known = DRIVER_FOR.find(([re]) => re.test(sink));
809
+ return `Pass a driver named ${sink} to run(): ` +
810
+ (known ? known[1](sink) : `run(App, { ${sink}: makeFetchDriver() }) for HTTP, or driverFromAsync(fn) / your own driver`) +
811
+ `. Check the spelling against the drivers you pass`;
812
+ };
799
813
  const wiringCheck = {
800
814
  id: 'wiring',
801
815
  onIntent(component, actionNames) {
@@ -1344,6 +1358,10 @@ onReset(() => {
1344
1358
  });
1345
1359
  const unref = (t) => { if (t && typeof t.unref === 'function')
1346
1360
  t.unref(); return t; };
1361
+ /**
1362
+ * The view's source, plus the words sygnal/vite registered for the component (its module's and
1363
+ * its relative imports' string literals: markup a view helper draws), when it did
1364
+ */
1347
1365
  function viewSource(component) {
1348
1366
  const view = component && component.view;
1349
1367
  if (typeof view !== 'function')
@@ -1356,6 +1374,9 @@ function viewSource(component) {
1356
1374
  catch (_) {
1357
1375
  src = '';
1358
1376
  }
1377
+ const registered = globalThis.__SYGNAL_VIEW_TOKENS__?.get?.(view);
1378
+ if (typeof registered == 'string')
1379
+ src += '\n' + registered;
1359
1380
  viewTokens.set(view, src);
1360
1381
  }
1361
1382
  return src;
@@ -3000,7 +3021,8 @@ const persistCheck = {
3000
3021
  const o = p.options, init = view.initialState;
3001
3022
  if (!o || !init || typeof init != 'object')
3002
3023
  return;
3003
- const keys = Object.keys(init);
3024
+ // a behavior's slice (`uses = { chat: chat() }`) is a state key too
3025
+ const keys = [...Object.keys(init), ...Object.keys(view.uses || {})];
3004
3026
  for (const opt of ['pick', 'omit']) {
3005
3027
  for (const k of Array.isArray(o[opt]) ? o[opt] : []) {
3006
3028
  if (keys.includes(k) || !once(`SYG223:${name}:${opt}:${k}`))
@@ -3714,6 +3736,18 @@ function removed(component, name, anchor, detail) {
3714
3736
  data: { form: anchor, guide: `${MIGRATE}#${anchor}`, ...(detail ? { detail } : {}) },
3715
3737
  });
3716
3738
  }
3739
+ /** SYG427: CHILD.select(x) with no component (an import of a name the module doesn't export) */
3740
+ function notComponent(f, x) {
3741
+ if (!once(`SYG427:${f.name}`))
3742
+ return;
3743
+ const got = x === null ? 'null' : typeof x == 'object' ? 'an object' : String(x);
3744
+ devReport('SYG427', {
3745
+ component: f.name,
3746
+ message: `${f.name}'s intent calls CHILD.select(${got}): that is not a component, so it never emits`,
3747
+ 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() { … }",
3748
+ data: { got },
3749
+ });
3750
+ }
3717
3751
  /** the removed forms a definition shows (statics, view arity, model keys), once per function */
3718
3752
  let seenDefs = new WeakSet();
3719
3753
  // G-332: resetChecks() forgets them (as the once() dedupe)
@@ -4097,8 +4131,13 @@ function nextHooks(_api) {
4097
4131
  const v = t[k];
4098
4132
  if (k !== 'CHILD' || !v || typeof v.select != 'function')
4099
4133
  return v;
4100
- return { ...v, select: (x, ...r) => { if (typeof x == 'string')
4101
- removed(f, f.name, 'child-select-name', `CHILD.select('${x}')`); return v.select(x, ...r); } };
4134
+ return { ...v, select: (x, ...r) => {
4135
+ if (typeof x == 'string')
4136
+ removed(f, f.name, 'child-select-name', `CHILD.select('${x}')`);
4137
+ else if (typeof x != 'function')
4138
+ notComponent(f, x);
4139
+ return v.select(x, ...r);
4140
+ } };
4102
4141
  },
4103
4142
  });
4104
4143
  },