sygnal 6.1.1 → 6.3.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 +100 -0
- package/README.md +3 -1
- package/dist/ai.cjs.js +36 -0
- package/dist/ai.cjs.js.map +1 -1
- package/dist/ai.esm.js +1 -1
- package/dist/astro/server.cjs.js +3 -3
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +3 -3
- package/dist/astro/server.mjs.map +1 -1
- package/dist/diagnostics.cjs.js +174 -6
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +174 -6
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/adapters.md +2 -1
- package/dist/guide/agent.md +119 -7
- package/dist/guide/ai-cards.md +319 -0
- package/dist/guide/ai-chat.md +64 -16
- package/dist/guide/ai-compose.md +881 -0
- package/dist/guide/ai-decisions.md +17 -1
- package/dist/guide/behaviors.md +1 -1
- package/dist/guide/error-boundaries.md +2 -1
- package/dist/guide/forms.md +1 -1
- package/dist/guide/http.md +5 -4
- package/dist/guide/mcp-apps.md +4 -2
- package/dist/guide/persistence.md +1 -1
- package/dist/guide/recipes/ai-form-fill.md +1 -1
- package/dist/guide/recipes/ai-summarize.md +1 -1
- package/dist/guide/recipes/ai-support-inbox.md +1 -1
- package/dist/guide/resources.md +11 -10
- package/dist/guide/ui/combobox.md +1 -1
- package/dist/guide/undo.md +1 -1
- package/dist/guide/web-components.md +1 -0
- package/dist/guide/webmcp.md +1 -2
- package/dist/index.cjs.js +5842 -370
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +679 -13
- package/dist/index.esm.js +5832 -370
- 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/vike/config/package.json +1 -1
- package/dist/vike/onRenderClient.cjs.js +3 -2
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +3 -2
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/llms.txt +8 -6
- package/package.json +2 -1
- package/src/ai.d.ts +683 -12
- package/src/ai.ts +6 -0
- package/src/collection.ts +2 -1
- package/src/core/instance.ts +6 -3
- package/src/core/markers/lazy.ts +7 -3
- package/src/core/markers/portal.ts +5 -2
- package/src/core/markers/suspense.ts +3 -2
- package/src/core/markers/transition.ts +6 -3
- package/src/core/registry.ts +9 -5
- package/src/cycle/dom/IsolateModule.ts +25 -22
- package/src/extra/ai/agent/index.ts +111 -26
- package/src/extra/ai/chat/behavior.ts +368 -87
- package/src/extra/ai/chat/driver.ts +7 -4
- package/src/extra/ai/compose/agent.ts +55 -0
- package/src/extra/ai/compose/ask.ts +147 -0
- package/src/extra/ai/compose/behavior.ts +183 -0
- package/src/extra/ai/compose/catalog.ts +211 -0
- package/src/extra/ai/compose/composed.ts +61 -0
- package/src/extra/ai/compose/decisions.ts +1649 -0
- package/src/extra/ai/compose/diag.ts +33 -0
- package/src/extra/ai/compose/index.ts +23 -0
- package/src/extra/ai/compose/layout.ts +441 -0
- package/src/extra/ai/compose/llm.ts +1120 -0
- package/src/extra/ai/compose/render.ts +159 -0
- package/src/extra/ai/compose/testing.ts +71 -0
- package/src/extra/ai/compose/types.ts +159 -0
- package/src/extra/ai/decide.ts +97 -1
- package/src/extra/ai/index.ts +6 -1
- package/src/extra/ai/webmcp.ts +5 -3
- package/src/extra/diagnostics/checks/next.ts +86 -2
- package/src/extra/diagnostics/codes.ts +64 -3
- package/src/extra/fetchDriver.ts +58 -27
- package/src/extra/lens.ts +15 -0
- package/src/extra/ssr.ts +3 -3
- package/src/extra/testing.ts +2 -0
- package/src/extra/widget.ts +3 -3
- package/src/index.d.ts +42 -1
- package/src/index.ts +8 -0
- package/src/lazy.ts +3 -2
- package/src/portal.ts +5 -2
- package/src/pragma/index.ts +2 -1
- package/src/suspense.ts +5 -2
- package/src/switchable.ts +2 -1
- package/src/transition.ts +5 -2
- package/src/vike/onRenderClient.ts +3 -2
|
@@ -239,9 +239,13 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
239
239
|
// PLAN-5 2-R (G-393; checks/state.ts; dev entry)
|
|
240
240
|
SYG238: 'Root component with a model but no initial state',
|
|
241
241
|
// PLAN-6 A-1 (sygnal/ai agent layer, src/extra/ai/agent; runtime, while an agent layer runs)
|
|
242
|
-
SYG240: 'Agent action input has no JSON Schema form',
|
|
243
|
-
SYG241: 'Agent read, when or label threw',
|
|
244
|
-
SYG243: 'Agent action input schema converted with losses',
|
|
242
|
+
SYG240: 'Agent action or query input has no JSON Schema form',
|
|
243
|
+
SYG241: 'Agent read, query, when, until or label threw',
|
|
244
|
+
SYG243: 'Agent action or query input schema converted with losses',
|
|
245
|
+
// agent queries (src/extra/ai/agent; runtime and sygnal-check)
|
|
246
|
+
SYG246: 'Agent query is not a synchronous function',
|
|
247
|
+
SYG247: 'Agent query has the tool name of an action',
|
|
248
|
+
SYG248: 'Agent action until names no resource',
|
|
245
249
|
// PLAN-6 A-2 (src/extra/ai/webmcp.ts; reported with the severity passed, dev)
|
|
246
250
|
SYG242: 'WebMCP tool over a size budget',
|
|
247
251
|
SYG244: 'Agent read returns strings, untrusted not declared',
|
|
@@ -272,6 +276,10 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
272
276
|
SYG423: 'Context change skipped a view whose output it changes',
|
|
273
277
|
SYG424: 'Duplicate Collection item id',
|
|
274
278
|
SYG425: 'isolatedState slice lacks initialState keys',
|
|
279
|
+
// PLAN-9 (D471): checks/next.ts; dev entry and sygnal-check
|
|
280
|
+
SYG426: 'Deprecated lowercase marker tag',
|
|
281
|
+
// PLAN-9 (D470): checks/next.ts; dev entry
|
|
282
|
+
SYG453: 'Two children with the same identity share one instance',
|
|
275
283
|
SYG601: "Retired in 6.0: Missing or invalid sources",
|
|
276
284
|
SYG602: 'Intent is not a function',
|
|
277
285
|
SYG603: 'Intent returned an invalid value',
|
|
@@ -335,6 +343,29 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
335
343
|
// PLAN-6 A-2: experimentalExposeWebMcp (src/extra/ai/webmcp.ts; dev)
|
|
336
344
|
SYG674: 'WebMCP not available',
|
|
337
345
|
SYG676: 'WebMCP registerTool rejected',
|
|
346
|
+
// ---- PLAN-9 compose (A: src/extra/ai/compose/; dev, reported with the severity passed) ----
|
|
347
|
+
SYG161: 'Catalog container render is a component with a model',
|
|
348
|
+
SYG162: 'Catalog part with neither component nor render',
|
|
349
|
+
SYG250: 'Layout names an unknown part or slot',
|
|
350
|
+
SYG251: 'Invalid param value in a layout',
|
|
351
|
+
SYG252: 'Record param from() does not return an array of { value }',
|
|
352
|
+
SYG450: 'Catalog in names an unknown container or slot',
|
|
353
|
+
SYG451: 'Composed has no catalog or from is not a composition',
|
|
354
|
+
SYG452: 'Layout lists a node key twice',
|
|
355
|
+
SYG681: 'Compose backend option problem',
|
|
356
|
+
// PLAN-9 B-1/B-2: compose.decisions() (src/extra/ai/compose/decisions.ts; explicit severities)
|
|
357
|
+
SYG680: 'Part is LLM-only under a decision backend',
|
|
358
|
+
SYG682: 'Decision model answered outside the criteria',
|
|
359
|
+
// PLAN-9 B-3: compose.llm() (src/extra/ai/compose/llm.ts; the compose status becomes 'error')
|
|
360
|
+
SYG683: 'LLM layout invalid after repair',
|
|
361
|
+
// PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
|
|
362
|
+
SYG253: 'Misspelled catalog declaration key',
|
|
363
|
+
SYG254: 'Catalog descriptions or options overlap',
|
|
364
|
+
// PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
|
|
365
|
+
SYG160: 'Result part rendered outside a card',
|
|
366
|
+
SYG684: 'Client tool shadows an agent tool',
|
|
367
|
+
SYG685: 'Answer for a tool call that is not open',
|
|
368
|
+
SYG686: 'Ask catalog without a result part',
|
|
338
369
|
// PLAN-4 GS-3: a11y lane (static only, sygnal-check)
|
|
339
370
|
SYG701: 'Click listener on a non-interactive element',
|
|
340
371
|
SYG702: 'Form field without an accessible label',
|
|
@@ -414,6 +445,9 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
414
445
|
SYG240: 'error',
|
|
415
446
|
SYG241: 'error',
|
|
416
447
|
SYG243: 'warn',
|
|
448
|
+
SYG246: 'error',
|
|
449
|
+
SYG247: 'warn',
|
|
450
|
+
SYG248: 'warn',
|
|
417
451
|
// PLAN-6 A-2 (WebMCP, reported with the severity passed)
|
|
418
452
|
SYG242: 'warn',
|
|
419
453
|
SYG244: 'warn',
|
|
@@ -450,6 +484,10 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
450
484
|
SYG423: 'warn',
|
|
451
485
|
SYG424: 'warn',
|
|
452
486
|
SYG425: 'warn',
|
|
487
|
+
// PLAN-9 (checks/next.ts): D471 a lowercase <collection> / <switchable> tag (deprecated in 6.3.0)
|
|
488
|
+
SYG426: 'warn',
|
|
489
|
+
// PLAN-9 (checks/next.ts): D470 key identity, two children with one identity
|
|
490
|
+
SYG453: 'warn',
|
|
453
491
|
SYG612: 'error',
|
|
454
492
|
// PLAN-4 D131 (element.ts, through the core bridge; a console warning in dev with diagnostics off)
|
|
455
493
|
SYG644: 'warn',
|
|
@@ -490,6 +528,16 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
490
528
|
// PLAN-6 A-2 (experimentalExposeWebMcp reports them with an explicit severity)
|
|
491
529
|
SYG674: 'info',
|
|
492
530
|
SYG676: 'warn',
|
|
531
|
+
// PLAN-9 B-1/B-2 (compose.decisions() reports them with an explicit severity)
|
|
532
|
+
SYG680: 'info',
|
|
533
|
+
SYG682: 'warn',
|
|
534
|
+
// PLAN-9 B-3 (compose.llm)
|
|
535
|
+
SYG683: 'error',
|
|
536
|
+
// PLAN-9 H (chat client tools and ask; reported with the severity passed)
|
|
537
|
+
SYG160: 'warn',
|
|
538
|
+
SYG684: 'warn',
|
|
539
|
+
SYG685: 'warn',
|
|
540
|
+
SYG686: 'warn',
|
|
493
541
|
// PLAN-5 V-1: VirtualCollection (checks/virtual.ts, through the core bridge)
|
|
494
542
|
SYG430: 'warn',
|
|
495
543
|
SYG431: 'warn',
|
|
@@ -521,6 +569,19 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
521
569
|
SYG153: 'warn',
|
|
522
570
|
SYG730: 'warn',
|
|
523
571
|
SYG731: 'warn',
|
|
572
|
+
// ---- PLAN-9 compose (A: src/extra/ai/compose/, reported with the severity passed, dev) ----
|
|
573
|
+
SYG161: 'error',
|
|
574
|
+
SYG162: 'error',
|
|
575
|
+
SYG250: 'warn',
|
|
576
|
+
SYG251: 'warn',
|
|
577
|
+
SYG252: 'warn',
|
|
578
|
+
SYG450: 'warn',
|
|
579
|
+
SYG451: 'warn',
|
|
580
|
+
SYG452: 'warn',
|
|
581
|
+
SYG681: 'error',
|
|
582
|
+
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
583
|
+
SYG253: 'warn',
|
|
584
|
+
SYG254: 'info',
|
|
524
585
|
}
|
|
525
586
|
|
|
526
587
|
export function getCodeInfo(code: string): DiagnosticCodeInfo | undefined {
|
package/src/extra/fetchDriver.ts
CHANGED
|
@@ -81,6 +81,12 @@ import {validateWith} from './standardSchema';
|
|
|
81
81
|
* the socket driver's backoff; the failure (with `attempts`) is delivered once, at the end.
|
|
82
82
|
* - `validate: schema` (a Standard Schema): the parsed body is validated (and transformed); a
|
|
83
83
|
* failure is an error with `issues`.
|
|
84
|
+
* - D472 (G-757): `batch: [request, …]` sends those requests in parallel instead of this one (each
|
|
85
|
+
* inherits its fields; its own `json` / `body` / `parse` win) and `combine(bodies, requests)`
|
|
86
|
+
* merges their parsed bodies into the one reply; `split(request, error)` turns a failed one (or
|
|
87
|
+
* the request itself) into requests sent again in parallel and combined, at most 3 deep. One
|
|
88
|
+
* failure fails the request once (`error.status` / `error.body`). No cache entry sharing, no
|
|
89
|
+
* per-part retry. The test fake (`_whole`) sends and answers it as one request. decide() uses it.
|
|
84
90
|
* - dispose / sink completion aborts everything in flight.
|
|
85
91
|
* - `fetch` is read at request time (options.fetch, else globalThis.fetch), so test stubs work.
|
|
86
92
|
* - PLAN-3 5-1 (H-9): renderComponent's HTTP fake runs this driver over an in-memory fetch;
|
|
@@ -137,6 +143,15 @@ const autoParse = async (res: any) => {
|
|
|
137
143
|
return text ? JSON.parse(text) : null;
|
|
138
144
|
};
|
|
139
145
|
|
|
146
|
+
/** a non-2xx response */
|
|
147
|
+
const bad = (res: any) => res.ok === false || res.status < 200 || res.status > 299;
|
|
148
|
+
/** the failure of a non-2xx response: `status`, `body` (parsed with 'auto') */
|
|
149
|
+
const httpError = async (res: any, href: string) => {
|
|
150
|
+
let body: any;
|
|
151
|
+
try { body = await autoParse(res); } catch (_) {}
|
|
152
|
+
return Object.assign(new Error(`HTTP ${res.status}${res.statusText ? ' ' + res.statusText : ''}: ${href}`), {status: res.status, body});
|
|
153
|
+
};
|
|
154
|
+
|
|
140
155
|
const PARSERS: Record<string, (res: any) => any> = {
|
|
141
156
|
auto: autoParse,
|
|
142
157
|
json: res => res.json(),
|
|
@@ -144,6 +159,8 @@ const PARSERS: Record<string, (res: any) => any> = {
|
|
|
144
159
|
response: res => res,
|
|
145
160
|
};
|
|
146
161
|
|
|
162
|
+
const parserOf = (pz: any) => typeof pz == 'function' ? pz : PARSERS[pz] || autoParse;
|
|
163
|
+
|
|
147
164
|
const pickInit = (init: any) => {
|
|
148
165
|
const out: any = {};
|
|
149
166
|
if (init && typeof init == 'object') INIT_KEYS.forEach(k => { if (init[k] !== undefined) out[k] = init[k]; });
|
|
@@ -379,15 +396,23 @@ export function makeFetchDriver(options: any = {}) {
|
|
|
379
396
|
if (abort) return cancel(r => r.scope === scope && (!('category' in req) || r.category === category));
|
|
380
397
|
if (rn !== undefined || (!pf && (latest ?? options.latest))) cancel(rk === undefined ? r => r.rk === undefined && r.scope === scope && r.category === category : r => r.sender === sender && r.rk === rk);
|
|
381
398
|
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
399
|
+
const base = options.init;
|
|
400
|
+
// a request's URL and fetch() init (the batch's sub-requests inherit the request's fields)
|
|
401
|
+
const prep = ({url, method, headers, query, json, body, init: own}: any) => {
|
|
402
|
+
const h = mergeHeaders(options.headers, base?.headers, own?.headers, headers);
|
|
403
|
+
let b = body ?? own?.body;
|
|
404
|
+
if (json !== undefined) {
|
|
405
|
+
b = JSON.stringify(json);
|
|
406
|
+
if (!Object.keys(h).some(k => k.toLowerCase() == 'content-type')) h['content-type'] = 'application/json';
|
|
407
|
+
}
|
|
408
|
+
return [withQuery((options.baseUrl || '') + (url ?? ''), query), {...pickInit(base), ...pickInit(own), method: method || own?.method || base?.method || (b !== undefined ? 'POST' : 'GET'), headers: h, body: b}];
|
|
409
|
+
};
|
|
410
|
+
const [href, fetchInit] = prep(req);
|
|
411
|
+
const m = fetchInit.method;
|
|
412
|
+
// D472: `batch` (sub-requests sent in parallel) and `split` (a failed request sent again in
|
|
413
|
+
// parts, at most 3 deep), their parsed bodies merged by `combine` into one reply. The test
|
|
414
|
+
// fake (_whole) answers such a request as one
|
|
415
|
+
const {batch, split, combine = (x: any) => x} = options._whole ? {} as any : req;
|
|
391
416
|
const k = C?.key(req, rn !== undefined || pf);
|
|
392
417
|
const id = ++seq;
|
|
393
418
|
const r: any = {category, scope, sender, rk};
|
|
@@ -421,18 +446,10 @@ export function makeFetchDriver(options: any = {}) {
|
|
|
421
446
|
const tries = pol.count ?? 3;
|
|
422
447
|
F = {id, k, q: req, s: new Set(), n: 0};
|
|
423
448
|
if (k !== undefined) C.f.set(k, F);
|
|
424
|
-
const
|
|
425
|
-
const parser = typeof pz == 'function' ? pz : PARSERS[pz] || autoParse;
|
|
449
|
+
const parser = parserOf(parse || options.parse);
|
|
426
450
|
// the global fetch is called as a method: a detached window.fetch throws "Illegal invocation"
|
|
427
451
|
const g: any = globalThis;
|
|
428
452
|
const doFetch = options.fetch || (typeof g.fetch == 'function' && ((u: string, i: any) => g.fetch(u, i)));
|
|
429
|
-
const fetchInit = {
|
|
430
|
-
...pickInit(base),
|
|
431
|
-
...pickInit(own),
|
|
432
|
-
method: m,
|
|
433
|
-
headers: h,
|
|
434
|
-
body: b,
|
|
435
|
-
};
|
|
436
453
|
// the outcome, to every request waiting on this fetch (the failure once, after the last attempt)
|
|
437
454
|
const end = (e: any, x?: any, status?: number) => {
|
|
438
455
|
done(F);
|
|
@@ -454,23 +471,37 @@ export function makeFetchDriver(options: any = {}) {
|
|
|
454
471
|
// 5-1 (H-9): renderComponent's fake learns which request (and resource) the next fetch is for
|
|
455
472
|
options._tap?.(req, rn, pf);
|
|
456
473
|
// called synchronously, so a test sees the call right after the sink emitted
|
|
457
|
-
p = Promise.resolve(doFetch(href, {...fetchInit, signal: ctl.signal}));
|
|
474
|
+
p = batch ? Promise.resolve() : Promise.resolve(doFetch(href, {...fetchInit, signal: ctl.signal}));
|
|
475
|
+
if (batch || split) {
|
|
476
|
+
// D472: parts in parallel; a failed one split again when `split` says how
|
|
477
|
+
const one = (q: any, d: number, first?: any): Promise<any> => {
|
|
478
|
+
const [u, i] = prep({...req, ...q});
|
|
479
|
+
return (first || Promise.resolve(doFetch(u, {...i, signal: ctl.signal}))).then(async (res: any) => {
|
|
480
|
+
if (bad(res)) {
|
|
481
|
+
const e = await httpError(res, u);
|
|
482
|
+
const qs = d < 3 && live() && split?.(q, e);
|
|
483
|
+
if (qs?.length > 1) return all(qs, d + 1);
|
|
484
|
+
throw e;
|
|
485
|
+
}
|
|
486
|
+
return parserOf(q.parse || parse || options.parse)(res);
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const all = (qs: any[], d: number) => Promise.all(qs.map(q => one(q, d))).then(bodies => combine(bodies, qs));
|
|
490
|
+
p = (batch ? all(batch, 0) : one(req, 0, p)).then(v => ({b: v}));
|
|
491
|
+
}
|
|
458
492
|
} catch (e) {
|
|
459
493
|
p = Promise.reject(e);
|
|
460
494
|
}
|
|
461
495
|
p.then(async (res: any) => {
|
|
496
|
+
if (batch || split) return live() && (k !== undefined && !F.z && C.put(k, req, res.b), end(0, res.b, 200));
|
|
462
497
|
if (!live()) return;
|
|
463
498
|
// R4-3: anything that throws here (a non-Response, a parser) fails the request
|
|
464
499
|
try {
|
|
465
500
|
const st = res.status;
|
|
466
|
-
if (res
|
|
467
|
-
|
|
468
|
-
try { errBody = await autoParse(res); } catch (_) {}
|
|
501
|
+
if (bad(res)) {
|
|
502
|
+
const e = await httpError(res, href);
|
|
469
503
|
if (!live() || ((st == 408 || st == 429 || st > 499) && again(res))) return;
|
|
470
|
-
|
|
471
|
-
e.status = st;
|
|
472
|
-
e.body = errBody;
|
|
473
|
-
return end(e, {status: st, body: errBody});
|
|
504
|
+
return end(e, {status: st, body: e.body});
|
|
474
505
|
}
|
|
475
506
|
let v: any;
|
|
476
507
|
try { v = await parser(res); } catch (e) { return live() && end(e, {status: st}); }
|
|
@@ -480,7 +511,7 @@ export function makeFetchDriver(options: any = {}) {
|
|
|
480
511
|
} catch (e) {
|
|
481
512
|
live() && end(e);
|
|
482
513
|
}
|
|
483
|
-
}, (e: any) => { live() && !again() && end(e); });
|
|
514
|
+
}, (e: any) => { live() && (e?.status ? end(e, {status: e.status, body: e.body}) : !again() && end(e)); });
|
|
484
515
|
};
|
|
485
516
|
go();
|
|
486
517
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PLAN-9 (D463): byId(field, id, idKey = 'id'), a { get, set } lens to the element of the array
|
|
3
|
+
* `state[field]` whose `idKey` is `id`: a record part's state (`state: (node) => byId('projects',
|
|
4
|
+
* node.params.id)`), or any child's `state={byId('todos', 3)}`. get: the element (undefined when it
|
|
5
|
+
* is gone); set: the array with that element replaced (the state unchanged when it is gone).
|
|
6
|
+
* Side-effect free.
|
|
7
|
+
*/
|
|
8
|
+
export const byId = (field: string, id: any, idKey = 'id') => ({
|
|
9
|
+
get: (s: any) => (Array.isArray(s?.[field]) ? s[field].find((x: any) => x?.[idKey] === id) : undefined),
|
|
10
|
+
set: (s: any, v: any) => {
|
|
11
|
+
const a = s?.[field]
|
|
12
|
+
if (!Array.isArray(a) || !a.some((x: any) => x?.[idKey] === id)) return s
|
|
13
|
+
return {...s, [field]: a.map((x: any) => (x?.[idKey] === id ? v : x))}
|
|
14
|
+
},
|
|
15
|
+
})
|
package/src/extra/ssr.ts
CHANGED
|
@@ -163,14 +163,14 @@ function withResources(def: any, state: any): any {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// PLAN-4 GS-9: uid(name?) as on the client: the root is 'u'; a child component adds its path in
|
|
166
|
-
// the parent's view (or its `id` prop), as getComponentIdFromElement and instantiateSubComponents
|
|
166
|
+
// the parent's view (or its vnode `key`, else its `id` prop, D456), as getComponentIdFromElement and instantiateSubComponents
|
|
167
167
|
// in the core do; a Collection item adds its key, a Switchable page its name; anything but
|
|
168
168
|
// each part encoded by uidPart (G-214); in the root option anything but [A-Za-z0-9_-] becomes '_'
|
|
169
169
|
// (as run() does). SSR ids = hydration ids
|
|
170
170
|
const makeUid = (base: string) => (n?: string) => n ? base + '-' + n : base
|
|
171
171
|
function childUid(uid: string, vnode: any, path: string): string {
|
|
172
|
-
const id = vnode.data?.props?.id
|
|
173
|
-
return uid + '-' + uidPart(('::' + ((id && JSON.stringify(id).replaceAll('"', '')) || path)).replace(/.*::(r\.)?/, ''))
|
|
172
|
+
const id = vnode.key ?? vnode.data?.props?.id
|
|
173
|
+
return uid + '-' + uidPart(('::' + ((id != null && JSON.stringify(id).replaceAll('"', '')) || path)).replace(/.*::(r\.)?/, ''))
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// G-567: a view that returns a string or a number renders it as text (core/instance.ts view());
|
package/src/extra/testing.ts
CHANGED
|
@@ -1754,6 +1754,8 @@ export function renderComponent(
|
|
|
1754
1754
|
// 5-3: focus / online come from t.focus() / t.online() only
|
|
1755
1755
|
_on: (f: any) => { signals.add(f); return () => signals.delete(f); },
|
|
1756
1756
|
_tap: (req: any, res?: string, pf?: any) => { tapped = [req, res, pf]; },
|
|
1757
|
+
// D472: a batch / split request (a large decide()) is one fetch here, answered as one
|
|
1758
|
+
_whole: true,
|
|
1757
1759
|
fetch: (url: string, init: any) => new Promise((resolve, reject) => {
|
|
1758
1760
|
const [req, res, pf] = tapped || [{url}];
|
|
1759
1761
|
tapped = undefined;
|
package/src/extra/widget.ts
CHANGED
|
@@ -171,14 +171,14 @@ function rewrite(n: any, o: any, path: string): any {
|
|
|
171
171
|
return r
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
/** widget markers among a Portal's children (the core doesn't walk them)
|
|
174
|
+
/** widget markers among a Portal's children (the core doesn't walk them): `pres.widget.$k`, which the
|
|
175
|
+
* Portal's rewrite calls (core/markers/portal.ts; G-751: either may be registered first) */
|
|
175
176
|
const inPortal = (c: any[], o: any, x: string): any[] =>
|
|
176
177
|
c.map((n, i) => n?.sel == 'widget' ? rewrite(n, o, x + '.' + i) : n?.children ? {...n, children: inPortal(n.children, o, x + '.' + i)} : n)
|
|
178
|
+
;(rewrite as any).$k = inPortal
|
|
177
179
|
|
|
178
180
|
export function defineWidget(def: any): any {
|
|
179
181
|
pres.widget ||= rewrite
|
|
180
|
-
const p: any = pres.portal
|
|
181
|
-
if (p && !p.$w) ((pres.portal = (n: any, o: any, x: string) => p({...n, children: inPortal(n.children || [], o, x)}, o, x)) as any).$w = 1
|
|
182
182
|
const w: any = () => {}, commands: any = {}
|
|
183
183
|
// D200: any name, `close` and `togglePopover` too (the core passes a widget command the options)
|
|
184
184
|
for (const m in def.commands) commands[m] = (el: any, o: any) => el[W] && def.commands[m](el[W].i, o || {}, el)
|
package/src/index.d.ts
CHANGED
|
@@ -2358,6 +2358,21 @@ export type FetchRequest = string | {
|
|
|
2358
2358
|
* schema's (possibly transformed) value. A failure is an error with `issues`
|
|
2359
2359
|
*/
|
|
2360
2360
|
validate?: StandardSchemaLike;
|
|
2361
|
+
/**
|
|
2362
|
+
* D472: send these sub-requests instead, in parallel (each inherits this request's fields:
|
|
2363
|
+
* `url`, `headers`, `init`, ...; its own `json` / `body` / `parse` win), and deliver ONE reply:
|
|
2364
|
+
* `combine(bodies, requests)`. One failure fails the request (once). The test fake
|
|
2365
|
+
* (renderComponent) answers the request as one, by its own `url` / `json`. `decide()` makes these
|
|
2366
|
+
*/
|
|
2367
|
+
batch?: ReadonlyArray<{ url?: string; method?: string; headers?: Record<string, string>; query?: Record<string, unknown>; json?: unknown; body?: BodyInit; parse?: 'auto' | 'json' | 'text' | ((response: Response) => any) }>;
|
|
2368
|
+
/** D472: merges the parsed bodies of `batch` / `split` parts into the reply (default: the array) */
|
|
2369
|
+
combine?: (bodies: any[], requests: any[]) => any;
|
|
2370
|
+
/**
|
|
2371
|
+
* D472: a request (or a `batch` part) that fails with an HTTP error is sent again as the
|
|
2372
|
+
* requests this returns (≥ 2, in parallel, merged by `combine`), at most 3 times deep; null keeps
|
|
2373
|
+
* the error. `decide()` halves its questions on a 400 / 413 that names a size limit
|
|
2374
|
+
*/
|
|
2375
|
+
split?: (request: any, error: Error & { status?: number; body?: any }) => any[] | null | undefined;
|
|
2361
2376
|
/** Your own fields (an id, ...): not sent, returned on the reply's `request` */
|
|
2362
2377
|
[appData: string]: any;
|
|
2363
2378
|
} | {
|
|
@@ -3708,7 +3723,9 @@ declare global {
|
|
|
3708
3723
|
namespace JSX {
|
|
3709
3724
|
interface IntrinsicElements {
|
|
3710
3725
|
[elemName: string]: any;
|
|
3726
|
+
/** @deprecated since 6.3.0, removed in the next major: `import { Collection } from 'sygnal'` and write `<Collection …>` (SYG426) */
|
|
3711
3727
|
collection: CollectionProps<any>;
|
|
3728
|
+
/** @deprecated since 6.3.0, removed in the next major: `import { Switchable } from 'sygnal'` and write `<Switchable …>` (SYG426) */
|
|
3712
3729
|
switchable: SwitchableProps<any>;
|
|
3713
3730
|
slot: SlotProps;
|
|
3714
3731
|
}
|
|
@@ -3721,6 +3738,15 @@ declare global {
|
|
|
3721
3738
|
children: {};
|
|
3722
3739
|
}
|
|
3723
3740
|
|
|
3741
|
+
/**
|
|
3742
|
+
* `key` on any tag (D456): on a component, its identity among its parent's children (before
|
|
3743
|
+
* its `id` prop, then its position): a keyed instance survives a move or a reorder, and a new
|
|
3744
|
+
* key makes a new instance. Never a prop of the component.
|
|
3745
|
+
*/
|
|
3746
|
+
interface IntrinsicAttributes {
|
|
3747
|
+
key?: string | number | null;
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3724
3750
|
/**
|
|
3725
3751
|
* A component's view receives `state: STATE` and `context`, but in JSX the parent passes
|
|
3726
3752
|
* an optional `state` slice name or lens (`<Editor state="editor" />`, `<Panel />`).
|
|
@@ -3733,7 +3759,7 @@ declare global {
|
|
|
3733
3759
|
export { messageText, withToolResults } from './ai.d'
|
|
3734
3760
|
export { decide, choice, noul, score } from './ai.d'
|
|
3735
3761
|
export { agentTools, toJsonSchema, parseInput, jsonSchema } from './ai.d'
|
|
3736
|
-
export type { AgentDeclaration, AgentAction, AgentSchema, AgentTool, AgentToolSet, AgentToolsOptions, AgentResult, AgentConfirm, AgentConfirmInfo, AgentIssue, ConvertedSchema, JsonSchemaObject, SchemaOutput } from './ai.d'
|
|
3762
|
+
export type { AgentDeclaration, AgentAction, AgentQuery, AgentQueryResult, AgentSchema, AgentTool, AgentToolSet, AgentToolsOptions, AgentResult, AgentConfirm, AgentConfirmInfo, AgentIssue, ConvertedSchema, JsonSchemaObject, SchemaOutput } from './ai.d'
|
|
3737
3763
|
// PLAN-6 L-1 (1-L)
|
|
3738
3764
|
export { makeChatDriver, outputJsonSchema } from './ai.d'
|
|
3739
3765
|
// PLAN-6 A-2 (2-W)
|
|
@@ -3754,6 +3780,21 @@ export type { ChatState, ChatStatus, ChatActions, ChatDone, ChatOptions } from '
|
|
|
3754
3780
|
// PLAN-6 M-2 / M-3 (3-M)
|
|
3755
3781
|
export { answers, commandBar } from './ai.d'
|
|
3756
3782
|
export type { AnswerPick, AnswerPicks, CommandBarState, CommandBarStatus, CommandBarActions, CommandBarOptions, CommandBarResult, CommandBarUnsure } from './ai.d'
|
|
3783
|
+
// PLAN-9 (A): catalogs, the compose behavior, composeWith, the layout utilities
|
|
3784
|
+
export { defineCatalog, compose, composeWith, fromTree, toTree, removeNode } from './ai.d'
|
|
3785
|
+
// PLAN-9 H: chat client tools and cards
|
|
3786
|
+
export { openToolCalls, askCard } from './ai.d'
|
|
3787
|
+
export { Composed } from './ai.d'
|
|
3788
|
+
export type { ChatClientTool, ChatAskOptions, ChatCard, ChatCardStatus, ChatToolPart, ComposedProps } from './ai.d'
|
|
3789
|
+
export type { Catalog, CatalogDecl, CatalogPartDecl, CatalogContainerDecl, CatalogParam, CatalogParamValue, CatalogParamsOf, CatalogRecord, CatalogPart, CatalogContainer, CatalogOption, CatalogTree, CatalogTreeNode, ComposeTree, ContainerView, Layout, LayoutNode, ComposeRenderOptions, ComposeRenderable, ComposeBackend, ComposeNames, ComposeState, ComposeStatus, ComposeActions, ComposeOptions, ComposeAnswer } from './ai.d'
|
|
3790
|
+
|
|
3791
|
+
/**
|
|
3792
|
+
* PLAN-9 (D463): a `{ get, set }` lens to the element of the array `state[field]` whose `idKey`
|
|
3793
|
+
* (default 'id') is `id`: a record part's state (`state: (node) => byId('projects',
|
|
3794
|
+
* node.params.id)`) or any child's `state={byId('todos', 3)}`. `get` is undefined once the element
|
|
3795
|
+
* is gone; `set` then leaves the state unchanged.
|
|
3796
|
+
*/
|
|
3797
|
+
export function byId<T = any, S = any>(field: string, id: unknown, idKey?: string): { get(state: S): T | undefined; set(state: S, value: T): S }
|
|
3757
3798
|
// PLAN-6 X-1 (3-X)
|
|
3758
3799
|
export { makeMcpAppDriver } from './ai.d'
|
|
3759
3800
|
export type { McpAppDriverOptions, McpAppSource, McpAppRequest, McpAppError, McpToolResult, McpHostContext, McpContentBlock, McpAppDisplayMode } from './ai.d'
|
package/src/index.ts
CHANGED
|
@@ -68,6 +68,14 @@ export { anthropicMessages, agui, fromAISDK, strictSchemas } from './extra/ai/in
|
|
|
68
68
|
export { makeMcpAppDriver } from './extra/ai/index'
|
|
69
69
|
// A-3 (3-A3, experimental, D291)
|
|
70
70
|
export { formTool } from './extra/ai/index'
|
|
71
|
+
// PLAN-9 (A): catalogs, the compose behavior, composeWith, the layout utilities
|
|
72
|
+
export { defineCatalog, compose, composeWith, fromTree, toTree, removeNode } from './extra/ai/index'
|
|
73
|
+
// PLAN-9 H: chat client tools and cards
|
|
74
|
+
export { openToolCalls, askCard } from './extra/ai/index'
|
|
75
|
+
// PLAN-9 M-1: <Composed catalog from /> (documented import: 'sygnal/ai')
|
|
76
|
+
export { Composed } from './extra/ai/index'
|
|
77
|
+
// PLAN-9 (D463): a lens to an array element by id
|
|
78
|
+
export { byId } from './extra/lens'
|
|
71
79
|
|
|
72
80
|
// export dom helper functions (h, makeDOMDriver, etc.)
|
|
73
81
|
export * from './cycle/dom/index'
|
package/src/lazy.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// the core's resolver for lazy components, registered
|
|
2
|
-
import './core/markers/lazy';
|
|
1
|
+
// the core's resolver for lazy components (D157), registered by the first lazy() call (G-751)
|
|
2
|
+
import {enableLazy} from './core/markers/lazy';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* PLAN-5 B-4 (D103): `when` defers the import: 'visible' starts it when a placeholder enters the
|
|
@@ -24,6 +24,7 @@ export interface LazyOptions {
|
|
|
24
24
|
const WHEN = 'data-sygnal-when';
|
|
25
25
|
|
|
26
26
|
export function lazy(loadFn: () => Promise<any>, options?: LazyOptions): any {
|
|
27
|
+
enableLazy();
|
|
27
28
|
let cachedComponent: any = null;
|
|
28
29
|
let loadError: any = null;
|
|
29
30
|
const g: any = globalThis, when = options?.when, ph = options?.placeholderHeight;
|
package/src/portal.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {h} from './cycle/dom/snabbdom';
|
|
2
|
-
// the core's handler for this marker, registered
|
|
3
|
-
import
|
|
2
|
+
// the core's handler for this marker (D157), registered when a tag uses it (G-751: the pragma calls
|
|
3
|
+
// `$reg`; not on import, so a bundle that never uses the marker drops the handler)
|
|
4
|
+
import {enablePortal as reg} from './core/markers/portal';
|
|
4
5
|
|
|
5
6
|
const Portal = (props: any) => {
|
|
7
|
+
reg();
|
|
6
8
|
const {children, ...sanitizedProps} = props;
|
|
7
9
|
return h('portal', {props: sanitizedProps}, children);
|
|
8
10
|
};
|
|
9
11
|
(Portal as any).componentName = 'portal';
|
|
10
12
|
(Portal as any).preventInstantiation = true;
|
|
13
|
+
(Portal as any).$reg = reg;
|
|
11
14
|
|
|
12
15
|
export {Portal};
|
|
13
16
|
export default Portal;
|
package/src/pragma/index.ts
CHANGED
|
@@ -322,7 +322,8 @@ export const createElementWithModules = (modules: Record<string, any>) => {
|
|
|
322
322
|
if (typeof text === 'undefined') plain = !!(flatten(children, kids = []) & +plain)
|
|
323
323
|
// G-492: a custom element (bit 8) routes list as a prop, in its own cache
|
|
324
324
|
const d = data ? sanitizeData(data, modules, isComponent ? croutes : t & 8 ? xroutes : routes, isComponent || t & 8 && 2) : {}
|
|
325
|
-
|
|
325
|
+
// G-751: a marker's core handler registers on first use (`$reg`), not when its module loads
|
|
326
|
+
if (fn) d[fn.preventInstantiation ? 'm' : 'c'] = fn, fn.$reg?.()
|
|
326
327
|
const key = k !== undefined ? k : data ? data.key : undefined
|
|
327
328
|
const vnode = plain ? new (Plain as any)(sel, d, kids, text, key) : { sel, data: d, children: kids, text, elm: undefined, key }
|
|
328
329
|
if (t & 2) applySvg(vnode)
|
package/src/suspense.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {h} from './cycle/dom/snabbdom';
|
|
2
|
-
// the core's handler for this marker, registered
|
|
3
|
-
import
|
|
2
|
+
// the core's handler for this marker (D157), registered when a tag uses it (G-751: the pragma calls
|
|
3
|
+
// `$reg`; not on import, so a bundle that never uses the marker drops the handler)
|
|
4
|
+
import {enableSuspense as reg} from './core/markers/suspense';
|
|
4
5
|
|
|
5
6
|
const Suspense = (props: any) => {
|
|
7
|
+
reg();
|
|
6
8
|
const {children, ...sanitizedProps} = props;
|
|
7
9
|
return h('suspense', {props: sanitizedProps}, children);
|
|
8
10
|
};
|
|
9
11
|
(Suspense as any).componentName = 'suspense';
|
|
10
12
|
(Suspense as any).preventInstantiation = true;
|
|
13
|
+
(Suspense as any).$reg = reg;
|
|
11
14
|
|
|
12
15
|
export {Suspense};
|
|
13
16
|
export default Suspense;
|
package/src/switchable.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {h} from './cycle/dom/snabbdom';
|
|
2
|
-
// the core's
|
|
2
|
+
// the core's host for this marker, registered on import (D157): unlike Portal, Transition and
|
|
3
|
+
// Suspense (G-751), not on first use, because the lowercase tag works without an import (docs)
|
|
3
4
|
import './core/hosts/switchable';
|
|
4
5
|
|
|
5
6
|
/** `<Switchable of={{ a: A, b: B }} current={state.tab} />`: a marker vnode the core's Switchable host renders */
|
package/src/transition.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {h} from './cycle/dom/snabbdom';
|
|
2
|
-
// the core's handler for this marker, registered
|
|
3
|
-
import
|
|
2
|
+
// the core's handler for this marker (D157), registered when a tag uses it (G-751: the pragma calls
|
|
3
|
+
// `$reg`; not on import, so a bundle that never uses the marker drops the handler)
|
|
4
|
+
import {enableTransition as reg} from './core/markers/transition';
|
|
4
5
|
|
|
5
6
|
const Transition = (props: any) => {
|
|
7
|
+
reg();
|
|
6
8
|
const {children, ...sanitizedProps} = props;
|
|
7
9
|
return h('transition', {props: sanitizedProps}, children);
|
|
8
10
|
};
|
|
9
11
|
(Transition as any).componentName = 'transition';
|
|
10
12
|
(Transition as any).preventInstantiation = true;
|
|
13
|
+
(Transition as any).$reg = reg;
|
|
11
14
|
|
|
12
15
|
export {Transition};
|
|
13
16
|
export default Transition;
|
|
@@ -105,7 +105,8 @@ function componentVNode(comp: any, key: string, stateField: any, children: any[]
|
|
|
105
105
|
children,
|
|
106
106
|
text: undefined,
|
|
107
107
|
elm: undefined,
|
|
108
|
-
key:
|
|
108
|
+
// no vnode key: it would be the instance identity (D456); `id` is (G-207)
|
|
109
|
+
key: undefined,
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -132,7 +133,7 @@ function pageChildVNode(pageState: any, stateField: any = 'page'): any {
|
|
|
132
133
|
children: [],
|
|
133
134
|
text: undefined,
|
|
134
135
|
elm: undefined,
|
|
135
|
-
key:
|
|
136
|
+
key: undefined,
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|