sygnal 6.6.0 → 7.0.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.
- package/CHANGELOG.md +132 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +32 -9
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +32 -9
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +87 -4
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +86 -5
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/accessibility.md +2 -0
- package/dist/guide/ai-chat.md +6 -6
- package/dist/guide/behaviors.md +1 -1
- package/dist/guide/element-commands.md +4 -2
- package/dist/guide/forms-reference.md +89 -3
- package/dist/guide/forms.md +8 -8
- package/dist/guide/http.md +77 -7
- package/dist/guide/migrating-to-7.md +314 -0
- package/dist/guide/recipes/ai-summarize.md +2 -1
- package/dist/guide/recipes/i18n.md +1 -1
- package/dist/guide/ui/tabs.md +5 -4
- package/dist/guide/ui/tooltip.md +24 -0
- package/dist/index.cjs.js +772 -318
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +198 -31
- package/dist/index.esm.js +772 -318
- package/dist/index.esm.js.map +1 -1
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui.cjs.js +96 -11
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.esm.js +96 -12
- package/dist/ui.esm.js.map +1 -1
- package/dist/vike/config/package.json +1 -1
- package/dist/vite/plugin.cjs.js +19 -9
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +19 -9
- package/dist/vite/plugin.mjs.map +1 -1
- package/llms.txt +17 -16
- package/package.json +1 -1
- package/src/astro/index.d.ts +8 -0
- package/src/astro/index.ts +18 -0
- package/src/core/actions.ts +28 -21
- package/src/core/define.ts +2 -0
- package/src/core/hooks.ts +2 -2
- package/src/core/hosts/collection.ts +4 -3
- package/src/core/instance.ts +60 -16
- package/src/core/runtime.ts +5 -1
- package/src/extra/ai/chat/behavior.ts +3 -3
- package/src/extra/ai/commandBar.ts +3 -3
- package/src/extra/ai/compose/behavior.ts +3 -3
- package/src/extra/ai/transports/shared.ts +8 -2
- package/src/extra/behaviors.ts +21 -0
- package/src/extra/diagnostics/checks/fetch.ts +21 -1
- package/src/extra/diagnostics/checks/forms.ts +4 -3
- package/src/extra/diagnostics/checks/index.ts +1 -0
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +12 -0
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +13 -0
- package/src/extra/fetchDriver.ts +112 -7
- package/src/extra/fetchEventSource.ts +113 -0
- package/src/extra/form.ts +68 -26
- package/src/extra/formHelpers.ts +3 -1
- package/src/extra/lens.ts +3 -2
- package/src/extra/queryCache.ts +4 -3
- package/src/extra/socketDriver.ts +10 -4
- package/src/extra/testing.ts +68 -11
- package/src/index.d.ts +197 -30
- package/src/index.ts +1 -0
- package/src/shared.ts +3 -0
- package/src/ui/dialog.ts +3 -2
- package/src/ui/tooltips.ts +92 -0
- package/src/ui.d.ts +35 -0
- package/src/ui.ts +1 -0
- package/src/vite/graph.ts +11 -3
- package/src/vite/plugin.d.ts +7 -0
- package/src/vite/plugin.ts +10 -5
package/dist/diagnostics.cjs.js
CHANGED
|
@@ -301,6 +301,8 @@ const CODE_TITLES = {
|
|
|
301
301
|
SYG634: 'latest: true with a computed key',
|
|
302
302
|
// PLAN-3 5-5 (D88)
|
|
303
303
|
SYG635: 'Caching asked for without a queryCache',
|
|
304
|
+
// PLAN-11 U-1: progress requests (dev entry, fetch.ts)
|
|
305
|
+
SYG636: "progress that can't be reported as asked",
|
|
304
306
|
// PLAN-4 GS-2: element commands (reported by the dev entry, checks/elementCommands.ts)
|
|
305
307
|
SYG640: 'Element command target not found',
|
|
306
308
|
SYG641: 'Unknown element command',
|
|
@@ -360,6 +362,9 @@ const CODE_TITLES = {
|
|
|
360
362
|
// PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
|
|
361
363
|
SYG253: 'Misspelled catalog declaration key',
|
|
362
364
|
SYG254: 'Catalog descriptions or options overlap',
|
|
365
|
+
SYG256: 'Output guard rejects the updated state',
|
|
366
|
+
SYG257: 'Output reads a field its action changed (7.0 timing audit)',
|
|
367
|
+
SYG454: 'State binding changed without a new component identity',
|
|
363
368
|
// PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
|
|
364
369
|
SYG160: 'Result part rendered outside a card',
|
|
365
370
|
SYG684: 'Client tool shadows an agent tool',
|
|
@@ -470,6 +475,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
470
475
|
SYG634: 'info',
|
|
471
476
|
// PLAN-3 5-5 (fetch.ts)
|
|
472
477
|
SYG635: 'warn',
|
|
478
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
479
|
+
SYG636: 'warn',
|
|
473
480
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
474
481
|
SYG640: 'warn',
|
|
475
482
|
SYG641: 'error',
|
|
@@ -586,6 +593,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
586
593
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
587
594
|
SYG253: 'warn',
|
|
588
595
|
SYG254: 'info',
|
|
596
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
597
|
+
SYG256: 'warn',
|
|
598
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
599
|
+
SYG257: 'info',
|
|
600
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
601
|
+
SYG454: 'warn',
|
|
589
602
|
};
|
|
590
603
|
function getCodeInfo(code) {
|
|
591
604
|
const severity = CODE_SEVERITY[code];
|
|
@@ -2211,14 +2224,15 @@ const formsCheck = {
|
|
|
2211
2224
|
});
|
|
2212
2225
|
const model = Object.keys(view.model || {}).map(a => a.split('|')[0].trim()), own = ownActions(b);
|
|
2213
2226
|
const clash = own.includes(o.submit);
|
|
2214
|
-
|
|
2227
|
+
// PLAN-11 F-3: with `request`, the form sends the submit itself and `submit` is optional
|
|
2228
|
+
if (o.request ? o.submit !== undefined && (typeof o.submit != 'string' || !model.includes(o.submit) || clash) : typeof o.submit != 'string' || !model.includes(o.submit) || clash) {
|
|
2215
2229
|
devReport('SYG234', {
|
|
2216
2230
|
component,
|
|
2217
|
-
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action, so a valid submit dispatches nothing`
|
|
2231
|
+
message: typeof o.submit != 'string' ? `${name}'s form '${key}' has no submit action and no request, so a valid submit dispatches nothing`
|
|
2218
2232
|
: clash ? `${name}'s form '${key}' names submit: '${o.submit}', one of the form's own actions; a valid submit dispatches the form's '${key}.${o.submit}'${model.includes(o.submit) ? `, never ${name}'s '${o.submit}' entry` : ''}`
|
|
2219
2233
|
: `${name}'s form '${key}' dispatches '${o.submit}' on a valid submit, but ${name} has no model entry '${o.submit}'`,
|
|
2220
2234
|
fix: clash ? SUBMIT_FIX(key, o.submit, own)
|
|
2221
|
-
: `
|
|
2235
|
+
: `Give the form a request, form(schema, { values, request: { url: '/api/signup' } }) (it sends the valid submit itself), or name a host action and add it to the model: submit: 'SIGN_UP', model: { SIGN_UP: { HTTP: (state, values) => ({ url, json: values, ok: '${key}.DONE', error: '${key}.ERRORS' }) } }`,
|
|
2222
2236
|
data: { key, submit: o.submit },
|
|
2223
2237
|
});
|
|
2224
2238
|
}
|
|
@@ -2643,6 +2657,13 @@ const routerCheck = {
|
|
|
2643
2657
|
const isStandardSchema = (schema) => !!schema && (typeof schema == 'object' || typeof schema == 'function') && typeof schema['~standard']?.validate == 'function';
|
|
2644
2658
|
|
|
2645
2659
|
const IDEMPOTENT = /^(GET|HEAD)$/i;
|
|
2660
|
+
// PLAN-11 U-1: fetch() init options a progress request (XMLHttpRequest) can't honour
|
|
2661
|
+
const XHR_IGNORES = ['keepalive', 'priority', 'cache', 'redirect', 'integrity', 'mode', 'referrer', 'referrerPolicy', 'window', 'duplex'];
|
|
2662
|
+
const reportProgress = (component, sink, url, why, fix) => {
|
|
2663
|
+
if (!once(`SYG636:${nameOf(component)}:${url}:${why}`))
|
|
2664
|
+
return;
|
|
2665
|
+
devReport('SYG636', { component, message: `${sink} ${url ? url + ' ' : ''}has progress, but ${why}`, fix, data: { sink, url } });
|
|
2666
|
+
};
|
|
2646
2667
|
/** instance → reply key lanes it has sent, and ok name → the other key it was sent under */
|
|
2647
2668
|
const lanes = new WeakMap();
|
|
2648
2669
|
const fetchSink = (component, name) => component?.sources?.[name]?.__sygnalStatic === 'resources';
|
|
@@ -2666,7 +2687,7 @@ function checkOne(component, sink, req, where) {
|
|
|
2666
2687
|
return;
|
|
2667
2688
|
const name = nameOf(component);
|
|
2668
2689
|
const url = typeof req.url === 'string' ? req.url : '';
|
|
2669
|
-
const method = String(req.method || (req.json !== undefined || req.body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2690
|
+
const method = String(req.method || (req.json !== undefined || req.body !== undefined || req.multipart !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
2670
2691
|
if ((req.cache === true || req.staleTime != null) && !IDEMPOTENT.test(method) && once(`SYG630:${name}:${method}:${url}`)) {
|
|
2671
2692
|
devReport('SYG630', {
|
|
2672
2693
|
component,
|
|
@@ -2677,6 +2698,17 @@ function checkOne(component, sink, req, where) {
|
|
|
2677
2698
|
}
|
|
2678
2699
|
if (req.cache === true || req.staleTime != null)
|
|
2679
2700
|
reportNoCache(component, sink, req.cache === true ? 'cache: true' : 'staleTime', url);
|
|
2701
|
+
if (req.progress) {
|
|
2702
|
+
if (where.includes('.resources.'))
|
|
2703
|
+
reportProgress(component, sink, url, 'resources report no progress (it is ignored)', 'Send the download as a request with progress and reply actions instead of a resource, or remove progress');
|
|
2704
|
+
else if (req.batch)
|
|
2705
|
+
reportProgress(component, sink, url, 'a batch request reports no progress (it is ignored)', 'Send the parts as separate requests with progress, or remove progress');
|
|
2706
|
+
else {
|
|
2707
|
+
const bad = XHR_IGNORES.filter(k => req.init && req.init[k] !== undefined);
|
|
2708
|
+
if (bad.length)
|
|
2709
|
+
reportProgress(component, sink, url, `it goes over XMLHttpRequest, which ignores init.${bad.join(', init.')}`, `Remove ${bad.join(', ')} from init for this request, or drop progress if the request needs ${bad.length > 1 ? 'them' : 'it'}`);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2680
2712
|
if ('validate' in req && !isStandardSchema(req.validate) && once(`SYG631:${name}:${url}`)) {
|
|
2681
2713
|
devReport('SYG631', {
|
|
2682
2714
|
component,
|
|
@@ -3777,6 +3809,52 @@ function installInspect() {
|
|
|
3777
3809
|
};
|
|
3778
3810
|
}
|
|
3779
3811
|
|
|
3812
|
+
/**
|
|
3813
|
+
* The 7.0 timing audit (SYG257, info): since 7.0 a model entry's STATE runs first and every other
|
|
3814
|
+
* sink gets the state it committed (6.x gave them the state from before the action). For code
|
|
3815
|
+
* written for 6.x, this reports each output sink that reads a top-level state field its action's
|
|
3816
|
+
* STATE changed: it gets the new value now. Fix: read `props.previousState.<field>` where the old
|
|
3817
|
+
* value is meant; a read meant to see the new value needs nothing. Opt-in, because in 7.0-native
|
|
3818
|
+
* code reading the new value is the intent. Turn it on with any of:
|
|
3819
|
+
* - configureTimingAudit(true) (this entry)
|
|
3820
|
+
* - renderComponent(C, { timingAudit: true }) (tests)
|
|
3821
|
+
* - sygnal({ diagnostics: { timingAudit: true } }) (sygnal/vite, dev)
|
|
3822
|
+
* - globalThis.__SYGNAL_TIMING_AUDIT__ = true
|
|
3823
|
+
* The flag lives on the core bridge (shared by every copy of this entry and renderComponent).
|
|
3824
|
+
* Mechanism: the dev hook layer's wrapHandler (checks/next.ts) wraps each non-STATE handler and
|
|
3825
|
+
* gives it the state through a Proxy that notes reads of changed top-level fields. Top-level
|
|
3826
|
+
* fields only (a nested change shows as its top-level field changing); once per component name,
|
|
3827
|
+
* action, sink and field. Nothing in production builds.
|
|
3828
|
+
*/
|
|
3829
|
+
/** Turn the timing audit on (true), off (false), or back to the default (globalThis.__SYGNAL_TIMING_AUDIT__). */
|
|
3830
|
+
function configureTimingAudit(on) {
|
|
3831
|
+
const core = bridge();
|
|
3832
|
+
if (core)
|
|
3833
|
+
core.timingAudit = on;
|
|
3834
|
+
}
|
|
3835
|
+
/** Whether the timing audit is on. */
|
|
3836
|
+
function isTimingAuditEnabled() {
|
|
3837
|
+
const flag = bridge()?.timingAudit;
|
|
3838
|
+
return flag === undefined ? globalThis.__SYGNAL_TIMING_AUDIT__ === true : flag === true;
|
|
3839
|
+
}
|
|
3840
|
+
/** handler `h` of `f`'s action `type`, sink `sink`, audited (f: the checks' instance facade) */
|
|
3841
|
+
const audited = (f, type, sink, h) => (s, d, nx, p, ...r) => {
|
|
3842
|
+
const was = p?.previousState;
|
|
3843
|
+
if (!isTimingAuditEnabled() || !was || was === s || !s || typeof s != 'object' || typeof was != 'object' || Array.isArray(s))
|
|
3844
|
+
return h(s, d, nx, p, ...r);
|
|
3845
|
+
const view = new Proxy(s, { get(t, k, rcv) {
|
|
3846
|
+
if (typeof k == 'string' && k in was && was[k] !== t[k] && once(`SYG257:${f.name}:${type}:${sink}:${k}`))
|
|
3847
|
+
devReport('SYG257', {
|
|
3848
|
+
component: f,
|
|
3849
|
+
message: `${f.name}'s '${type}' ${sink} read state.${k}, which the same action's STATE changed: since 7.0 it gets the new value (6.x gave it the old one)`,
|
|
3850
|
+
fix: `If it meant the value from before the action, read props.previousState.${k} (the 4th argument); if it meant the new value, nothing to change`,
|
|
3851
|
+
data: { action: type, sink, field: k },
|
|
3852
|
+
});
|
|
3853
|
+
return Reflect.get(t, k, rcv);
|
|
3854
|
+
} });
|
|
3855
|
+
return h(view, d, nx, p, ...r);
|
|
3856
|
+
};
|
|
3857
|
+
|
|
3780
3858
|
/**
|
|
3781
3859
|
* PLAN-4.6 R4: the dev entry's checks on the next component core (src/core/).
|
|
3782
3860
|
*
|
|
@@ -4289,6 +4367,9 @@ function nextHooks(_api) {
|
|
|
4289
4367
|
onAction(iv, a) { log.onAction(iv, a); },
|
|
4290
4368
|
wrapHandler(iv, type, sink, fn) {
|
|
4291
4369
|
let h = log.wrapHandler(iv, type, sink, fn) || fn;
|
|
4370
|
+
// 7.0 timing audit (SYG257, opt-in; checks/timingAudit.ts): checked per call, so turning it on later counts
|
|
4371
|
+
if (on() && sink != 'STATE' && typeof h == 'function')
|
|
4372
|
+
h = audited(fac(iv), type, sink, h);
|
|
4292
4373
|
// SYG222: the STATE reducers, wrapped (today the check rewrote the instance's model)
|
|
4293
4374
|
if (on() && sink == 'STATE' && typeof fn == 'function')
|
|
4294
4375
|
h = watchMutation(fac(iv), type, h);
|
|
@@ -4508,10 +4589,12 @@ exports.checkEventBus = checkEventBus;
|
|
|
4508
4589
|
exports.checks = checks;
|
|
4509
4590
|
exports.configureChecks = configureChecks;
|
|
4510
4591
|
exports.configureStrict = configureStrict;
|
|
4592
|
+
exports.configureTimingAudit = configureTimingAudit;
|
|
4511
4593
|
exports.getCodeInfo = getCodeInfo;
|
|
4512
4594
|
exports.inspect = inspect;
|
|
4513
4595
|
exports.installChecks = installChecks;
|
|
4514
4596
|
exports.isStrictEnabled = isStrictEnabled;
|
|
4597
|
+
exports.isTimingAuditEnabled = isTimingAuditEnabled;
|
|
4515
4598
|
exports.listCodes = listCodes;
|
|
4516
4599
|
exports.resetChecks = resetChecks;
|
|
4517
4600
|
//# sourceMappingURL=diagnostics.cjs.js.map
|