sygnal 6.5.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 +156 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +423 -27
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +423 -27
- 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 +141 -3
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.esm.js +141 -4
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +169 -14
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +168 -15
- 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 +781 -320
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +319 -43
- package/dist/index.esm.js +781 -320
- 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/vike/onRenderClient.cjs.js +63 -43
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +63 -43
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +21 -25
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +21 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +410 -27
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +410 -27
- 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 +8 -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/core/view.ts +2 -0
- package/src/cycle/dom/utils.ts +4 -2
- package/src/devtools.d.ts +13 -0
- package/src/devtools.ts +3 -0
- 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/devGraph.ts +111 -0
- package/src/extra/devMcp.ts +1 -1
- package/src/extra/devtools.ts +18 -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 +2 -1
- package/src/extra/diagnostics/checks/inspect.ts +68 -6
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +133 -12
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +19 -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 +199 -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/vike/onRenderClient.ts +44 -46
- package/src/vike/onRenderHtml.ts +5 -26
- package/src/vike/shell.ts +33 -0
- package/src/vite/graph.ts +255 -0
- package/src/vite/mcp.ts +3 -3
- package/src/vite/plugin.d.ts +19 -0
- package/src/vite/plugin.ts +144 -25
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
* onRender (PLAN-4 CT-1) the controls the instance renders: its own vtree, up to the
|
|
18
18
|
* isolated child components, through ./controls' vnode -> control map
|
|
19
19
|
* onDispose prunes the instance
|
|
20
|
+
* (PLAN-10 D522, read from the instance view at inspect() time, no core change) viewOnly (the
|
|
21
|
+
* component function has no statics), entry (an app's root), app (which run() the
|
|
22
|
+
* instance belongs to), shown: false (a hidden Switchable page and below), the
|
|
23
|
+
* instance ids on children entries and in events[type].emitterIds / selectorIds.
|
|
24
|
+
* D530: contextConsumes from InstanceView.reads (the keys the latest render read);
|
|
25
|
+
* diagnostics keep their `timestamp`.
|
|
26
|
+
* `key` (PLAN-10 P-2, D544): the static 'file#name' (D521) that sygnal/vite stamps on
|
|
27
|
+
* the component function in dev (a non-enumerable `__sygnalKey`), when it is there;
|
|
28
|
+
* the runtime itself has no source file
|
|
20
29
|
* (PLAN-4 2-C) the action log (./actionLog: actionHooks, a layer of checks/next.ts) feeds
|
|
21
30
|
* inspect({ actions }): the recent actions (the last RECENT_MAX, any instance)
|
|
22
31
|
* Selectors come from the DOM check (real DOM) or from renderComponent (mock
|
|
@@ -205,6 +214,8 @@ const slim = (d: any): InspectDiagnostic => {
|
|
|
205
214
|
if (d.component) out.component = d.component
|
|
206
215
|
if (d.fix) out.fix = d.fix
|
|
207
216
|
if (d.docsUrl) out.docsUrl = d.docsUrl
|
|
217
|
+
// PLAN-10 P-1: when it was reported (runtime diagnostics are reported once per process)
|
|
218
|
+
if (typeof d.timestamp === 'number') out.timestamp = d.timestamp
|
|
208
219
|
if (d.data !== undefined) {
|
|
209
220
|
// keep it JSON-safe (data can hold errors, vnodes, ...)
|
|
210
221
|
try { out.data = JSON.parse(JSON.stringify(d.data)) } catch (_) { /* drop */ }
|
|
@@ -316,12 +327,55 @@ function childrenOf(r: Rec, all: Rec[]): InspectChild[] {
|
|
|
316
327
|
if (c.parentId !== r.id) continue
|
|
317
328
|
const key = `${c.name}\u0000${via[c.kind]}`
|
|
318
329
|
const entry = out.get(key)
|
|
319
|
-
if (entry) entry.count =
|
|
320
|
-
else out.set(key, {name: c.name, via: via[c.kind], count: 1})
|
|
330
|
+
if (entry) entry.count = entry.ids!.push(c.id)
|
|
331
|
+
else out.set(key, {name: c.name, via: via[c.kind], count: 1, ids: [c.id]})
|
|
321
332
|
}
|
|
322
333
|
return [...out.values()]
|
|
323
334
|
}
|
|
324
335
|
|
|
336
|
+
/** the statics sygnal-check reads (its STATIC_PROPS): a component function with none is view-only */
|
|
337
|
+
const STATICS = ['intent', 'model', 'initialState', 'isolatedState', 'context', 'calculated', 'connections', 'resources', 'route', 'head', 'uses', 'timers', 'persist', 'viewTransitions', 'browser', 'agent']
|
|
338
|
+
|
|
339
|
+
/** PLAN-10 D522: no statics and nothing the core added (renderComponent's test intent, a behavior's model) */
|
|
340
|
+
function isViewOnly(c: any): boolean {
|
|
341
|
+
const def = c && c.__next && c.__next.def
|
|
342
|
+
if (!def || def.intent || (def.handlers && def.handlers.size) || def.initialState !== undefined) return false
|
|
343
|
+
const fn = def.view
|
|
344
|
+
return !!fn && STATICS.every(k => fn[k] === undefined)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** PLAN-10 P-2 (D544): the static key sygnal/vite's dev transform stamped on the component function */
|
|
348
|
+
function stampedKey(c: any): string | undefined {
|
|
349
|
+
const fn = c && c.__next && c.__next.def && c.__next.def.view
|
|
350
|
+
const k = fn && fn.__sygnalKey
|
|
351
|
+
return typeof k === 'string' ? k : undefined
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** PLAN-10 D530: the context keys the instance's latest render read (InstanceView.reads); every key when it read them all */
|
|
355
|
+
function contextReadsOf(c: any): string[] {
|
|
356
|
+
const v = c && c.__next
|
|
357
|
+
const reads: ReadonlySet<string> | null | undefined = v && v.reads
|
|
358
|
+
if (!reads) return []
|
|
359
|
+
const out = [...reads].filter(k => k !== '\u0000all')
|
|
360
|
+
if (reads.has('\u0000all')) { try { for (const k of Object.keys(v.context || {})) if (!out.includes(k)) out.push(k) } catch (_) { /* no context */ } }
|
|
361
|
+
return out
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** PLAN-10 D522: the app (run()) of each instance: the index of its root among the live roots, oldest first */
|
|
365
|
+
function appsOf(all: Rec[]): Map<Rec, number> {
|
|
366
|
+
const byId = new Map(all.map(r => [r.id, r]))
|
|
367
|
+
const roots = all.filter(r => r.parentId === null).sort((a, b) => Number(a.id) - Number(b.id))
|
|
368
|
+
const index = new Map(roots.map((r, i) => [r, i]))
|
|
369
|
+
const out = new Map<Rec, number>()
|
|
370
|
+
for (const r of all) {
|
|
371
|
+
let top: Rec | undefined = r
|
|
372
|
+
for (let n = 0; top && top.parentId !== null && n < 10000; n++) top = byId.get(top.parentId)
|
|
373
|
+
const i = top && index.get(top)
|
|
374
|
+
if (i !== undefined) out.set(r, i)
|
|
375
|
+
}
|
|
376
|
+
return out
|
|
377
|
+
}
|
|
378
|
+
|
|
325
379
|
/** The app graph of the live component instances (see ./public.d.ts). */
|
|
326
380
|
export function inspect(options: InspectOptions = {}): InspectGraph {
|
|
327
381
|
// report SYG105 (info) for EVENTS types selected but never emitted so far
|
|
@@ -340,22 +394,29 @@ export function inspect(options: InspectOptions = {}): InspectGraph {
|
|
|
340
394
|
const ids = options.ids && new Set(options.ids.map(String))
|
|
341
395
|
const recs = ids ? all.filter(r => ids.has(r.id)) : all
|
|
342
396
|
const names = new Set(recs.map(r => r.name))
|
|
397
|
+
const apps = appsOf(all)
|
|
343
398
|
|
|
344
399
|
const components: InspectComponent[] = recs.map(r => {
|
|
345
400
|
const c = r.instance
|
|
346
401
|
const calculated = c && c.calculated && typeof c.calculated === 'object' ? Object.keys(c.calculated) : []
|
|
347
402
|
const selectors = selectorsOf(r, options, diags)
|
|
348
403
|
const controls = controlsOf(r, selectors)
|
|
404
|
+
const key = stampedKey(c)
|
|
349
405
|
return {
|
|
350
406
|
name: r.name,
|
|
351
407
|
id: r.id,
|
|
408
|
+
...(key ? {key} : {}),
|
|
352
409
|
parentId: r.parentId,
|
|
353
410
|
kind: r.kind,
|
|
411
|
+
...(isViewOnly(c) ? {viewOnly: true as const} : {}),
|
|
412
|
+
...(r.parentId === null ? {entry: true as const} : {}),
|
|
413
|
+
...(apps.has(r) ? {app: apps.get(r)} : {}),
|
|
414
|
+
...(c && c.__next && c.__next.shown === false ? {shown: false as const} : {}),
|
|
354
415
|
actions: actionsOf(r),
|
|
355
416
|
stateKeys: stateKeysOf(c, calculated),
|
|
356
417
|
calculated,
|
|
357
418
|
contextProvides: c && isPlainObject(c.context) ? Object.keys(c.context) : [],
|
|
358
|
-
contextConsumes:
|
|
419
|
+
contextConsumes: contextReadsOf(c),
|
|
359
420
|
eventsEmitted: [...r.eventsEmitted],
|
|
360
421
|
eventsSelected: [...r.eventsSelected],
|
|
361
422
|
children: childrenOf(r, all),
|
|
@@ -368,11 +429,12 @@ export function inspect(options: InspectOptions = {}): InspectGraph {
|
|
|
368
429
|
|
|
369
430
|
const cache = cacheOf(recs) || {}
|
|
370
431
|
const events: InspectGraph['events'] = {}
|
|
371
|
-
|
|
432
|
+
// names once each (as before); PLAN-10 D522: every instance's id in emitterIds / selectorIds
|
|
433
|
+
const ev = (type: string) => events[type] || (events[type] = {emitters: [], selectors: [], emitterIds: [], selectorIds: []})
|
|
372
434
|
const add = (list: string[], name: string) => { if (!list.includes(name)) list.push(name) }
|
|
373
435
|
for (const c of components) {
|
|
374
|
-
for (const t of c.eventsEmitted)
|
|
375
|
-
for (const t of c.eventsSelected)
|
|
436
|
+
for (const t of c.eventsEmitted) { const e = ev(t); add(e.emitters, c.name); e.emitterIds!.push(c.id) }
|
|
437
|
+
for (const t of c.eventsSelected) { const e = ev(t); add(e.selectors, c.name); e.selectorIds!.push(c.id) }
|
|
376
438
|
}
|
|
377
439
|
// emitted on the bus by something that isn't a listed component (e.g. a driver, a pruned component)
|
|
378
440
|
if (!ids) for (const t of busTypes) ev(t)
|
|
@@ -28,6 +28,7 @@ import {watchMutation} from './state'
|
|
|
28
28
|
import {checkRequest} from './replies'
|
|
29
29
|
import {check as checkFetch} from './fetch'
|
|
30
30
|
import {checkSentCommand} from './elementCommands'
|
|
31
|
+
import {audited} from './timingAudit'
|
|
31
32
|
import {checkCollection} from './collections'
|
|
32
33
|
import {eventEmitted, actionListener as recentActions} from './inspect'
|
|
33
34
|
import {actionHooks} from './actionLog'
|
|
@@ -444,6 +445,8 @@ export function nextHooks(_api: any): any {
|
|
|
444
445
|
onAction(iv: any, a: any) { log.onAction(iv, a) },
|
|
445
446
|
wrapHandler(iv: any, type: string, sink: string, fn: any) {
|
|
446
447
|
let h = log.wrapHandler(iv, type, sink, fn) || fn
|
|
448
|
+
// 7.0 timing audit (SYG257, opt-in; checks/timingAudit.ts): checked per call, so turning it on later counts
|
|
449
|
+
if (on() && sink != 'STATE' && typeof h == 'function') h = audited(fac(iv), type, sink, h)
|
|
447
450
|
// SYG222: the STATE reducers, wrapped (today the check rewrote the instance's model)
|
|
448
451
|
if (on() && sink == 'STATE' && typeof fn == 'function') h = watchMutation(fac(iv), type, h)
|
|
449
452
|
return h === fn ? undefined : h
|
|
@@ -40,6 +40,18 @@ export interface DiagnosticCheck {
|
|
|
40
40
|
*/
|
|
41
41
|
export function configureStrict(on?: boolean): void
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* 7.0 timing audit (SYG257, info), for migrating 6.x code: report each output sink that reads a
|
|
45
|
+
* top-level state field its action's STATE changed (since 7.0 it gets the new value; 6.x gave it
|
|
46
|
+
* the old one; read props.previousState for the old one). true / false, or undefined for the
|
|
47
|
+
* default (globalThis.__SYGNAL_TIMING_AUDIT__ === true). Also renderComponent(C, { timingAudit })
|
|
48
|
+
* and sygnal({ diagnostics: { timingAudit: true } }). Diagnostics must be on.
|
|
49
|
+
*/
|
|
50
|
+
export function configureTimingAudit(on?: boolean): void
|
|
51
|
+
|
|
52
|
+
/** Whether the timing audit is on. */
|
|
53
|
+
export function isTimingAuditEnabled(): boolean
|
|
54
|
+
|
|
43
55
|
/** Whether the runtime strict checks are on. */
|
|
44
56
|
export function isStrictEnabled(): boolean
|
|
45
57
|
|
|
@@ -96,9 +108,23 @@ export function getCodeInfo(code: string): DiagnosticCodeInfo | undefined
|
|
|
96
108
|
|
|
97
109
|
/** How an action is dispatched. */
|
|
98
110
|
/** 'agent' (static, PLAN-6 K-1): only the component's `agent` declaration dispatches it */
|
|
99
|
-
|
|
111
|
+
/**
|
|
112
|
+
* 'behavior' (static, PLAN-10 A-8): a `uses` entry's action nothing else triggers that the behavior
|
|
113
|
+
* may dispatch itself where the checker can't list it (a package behavior, an open model, ...)
|
|
114
|
+
*/
|
|
115
|
+
export type InspectActionTrigger = 'intent' | 'next' | 'reply' | 'builtin' | 'agent' | 'behavior' | 'unknown'
|
|
100
116
|
|
|
101
|
-
|
|
117
|
+
/**
|
|
118
|
+
* PLAN-10 A-4 (static only): a source position, 1-based. `file` (relative to the working
|
|
119
|
+
* directory) only when it isn't the component's file.
|
|
120
|
+
*/
|
|
121
|
+
export interface InspectLocation {
|
|
122
|
+
line: number
|
|
123
|
+
column: number
|
|
124
|
+
file?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface InspectAction extends Partial<InspectLocation> {
|
|
102
128
|
name: string
|
|
103
129
|
/**
|
|
104
130
|
* 'intent': returned by the component's intent. 'builtin': BOOTSTRAP,
|
|
@@ -112,18 +138,56 @@ export interface InspectAction {
|
|
|
112
138
|
trigger: InspectActionTrigger
|
|
113
139
|
/** sinks of the model entry (STATE, EVENTS, EFFECT, PARENT, custom drivers); [] without a model entry */
|
|
114
140
|
sinks: string[]
|
|
141
|
+
/** the `uses` key of the behavior that owns it ('pager' for 'pager.NEXT') */
|
|
142
|
+
behavior?: string
|
|
143
|
+
/** static, PLAN-10 A-4: the model entry (line/column on the action: the intent property, else this) */
|
|
144
|
+
modelAt?: InspectLocation
|
|
145
|
+
/**
|
|
146
|
+
* static, PLAN-10 A-7 (actions with a STATE sink only): the top-level state keys the STATE
|
|
147
|
+
* reducer(s) may write (a behavior's: its slice); [] when it never changes state; null when unknown
|
|
148
|
+
*/
|
|
149
|
+
writes?: string[] | null
|
|
150
|
+
/** static, PLAN-10 A-7: a STATE reducer may return undefined (removes a Collection item) */
|
|
151
|
+
removes?: true
|
|
115
152
|
}
|
|
116
153
|
|
|
117
|
-
export interface InspectChild {
|
|
154
|
+
export interface InspectChild extends Partial<InspectLocation> {
|
|
118
155
|
name: string
|
|
119
|
-
|
|
156
|
+
/**
|
|
157
|
+
* 'layout' (static, PLAN-10 A-9): Vike's shell, a +Wrapper / +Layout rendering the next +Layout in
|
|
158
|
+
* or a +Page it applies to (Wrapper(s) > Layout(s) > Page); no location (no JSX site)
|
|
159
|
+
*/
|
|
160
|
+
via: 'tag' | 'collection' | 'switchable' | 'slot' | 'layout'
|
|
120
161
|
/** Collection: the `from` state field, when known */
|
|
121
162
|
from?: string | null
|
|
122
163
|
/** runtime: number of live instances behind this entry (e.g. Collection items) */
|
|
123
164
|
count?: number
|
|
165
|
+
/** runtime, PLAN-10 D522: the ids of those instances */
|
|
166
|
+
ids?: string[]
|
|
167
|
+
/**
|
|
168
|
+
* static, PLAN-10 A-3: the id of the component it renders (resolved through the import); null when
|
|
169
|
+
* it isn't in the graph (a package, a widget, a file not scanned, a dynamic value)
|
|
170
|
+
*/
|
|
171
|
+
id?: string | null
|
|
172
|
+
/** static, PLAN-10 A-1: a literal `state` lens (`<Greeting state="host" />`) */
|
|
173
|
+
state?: string
|
|
174
|
+
/** static, PLAN-10 A-2: the package the tag is imported from ('sygnal/ui') */
|
|
175
|
+
external?: string
|
|
176
|
+
/** static, PLAN-10 A-2: a widget tag (defineWidget, fromZag, fromReact, the sygnal/ui Zag parts) */
|
|
177
|
+
widget?: true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** PLAN-10 A-5 (static): a CHILD.select(Child) call in an intent */
|
|
181
|
+
export interface InspectChildSelect extends InspectLocation {
|
|
182
|
+
/** the selected component's name as written */
|
|
183
|
+
child: string | null
|
|
184
|
+
/** its id; null when it isn't in the graph */
|
|
185
|
+
id: string | null
|
|
186
|
+
/** the intent actions whose stream contains the call (the receivers of the child's PARENT sink) */
|
|
187
|
+
actions: string[]
|
|
124
188
|
}
|
|
125
189
|
|
|
126
|
-
export interface InspectSelector {
|
|
190
|
+
export interface InspectSelector extends Partial<InspectLocation> {
|
|
127
191
|
/** CSS selector passed to DOM.select() (chained selects joined with a space) */
|
|
128
192
|
selector: string
|
|
129
193
|
/** event types listened to; null when unknown (runtime, real DOM) */
|
|
@@ -139,10 +203,12 @@ export interface InspectSelector {
|
|
|
139
203
|
isolationHit: string | null
|
|
140
204
|
/** PLAN-4 CT-1: the control this selector is (its key), when it is `[data-control="<Key>"]` */
|
|
141
205
|
control?: string
|
|
206
|
+
/** the `uses` key of the behavior that listens to it */
|
|
207
|
+
behavior?: string
|
|
142
208
|
}
|
|
143
209
|
|
|
144
210
|
/** PLAN-4 CT-1: a control (controls()) a component renders */
|
|
145
|
-
export interface InspectControl {
|
|
211
|
+
export interface InspectControl extends Partial<InspectLocation> {
|
|
146
212
|
/** the control's key (rendered as data-control="<name>") */
|
|
147
213
|
name: string
|
|
148
214
|
/** the intrinsic tag of a tag spec; null for a spec object */
|
|
@@ -157,10 +223,14 @@ export interface InspectDiagnostic {
|
|
|
157
223
|
code: string
|
|
158
224
|
severity: DiagnosticSeverity
|
|
159
225
|
component?: string
|
|
226
|
+
/** static, PLAN-10 A-3: the id of the component it is attached to */
|
|
227
|
+
componentId?: string
|
|
160
228
|
message: string
|
|
161
229
|
fix?: string
|
|
162
230
|
docsUrl?: string
|
|
163
231
|
data?: any
|
|
232
|
+
/** runtime only: when it was reported (ms since the epoch; each is reported once per process) */
|
|
233
|
+
timestamp?: number
|
|
164
234
|
/** static only */
|
|
165
235
|
file?: string
|
|
166
236
|
line?: number
|
|
@@ -215,6 +285,12 @@ export interface InspectComponent {
|
|
|
215
285
|
name: string
|
|
216
286
|
/** runtime: instance number (as a string); static: 'file:line' of the definition */
|
|
217
287
|
id: string
|
|
288
|
+
/**
|
|
289
|
+
* static, PLAN-10 D521: 'file#name', stable when lines move (hosts key selection and positions by it);
|
|
290
|
+
* a second component of the same name in one file gets 'file#name#2', and so on. Runtime (PLAN-10 P-2,
|
|
291
|
+
* D544): the same key when sygnal/vite's dev server stamped the component function, else absent
|
|
292
|
+
*/
|
|
293
|
+
key?: string
|
|
218
294
|
/** runtime: the parent instance's id; static: null (see the parents' `children`) */
|
|
219
295
|
parentId: string | null
|
|
220
296
|
/** static only: source file, relative to the working directory */
|
|
@@ -224,6 +300,32 @@ export interface InspectComponent {
|
|
|
224
300
|
* component renders it, else how it is first rendered
|
|
225
301
|
*/
|
|
226
302
|
kind: 'root' | 'child' | 'collection-item' | 'switchable'
|
|
303
|
+
/** PLAN-10 A-1 / D522: rendered as a component (or started as an entry) but has no statics */
|
|
304
|
+
viewOnly?: true
|
|
305
|
+
/**
|
|
306
|
+
* static, PLAN-10 A-6: started by run(), defineElement() or a Vike +Page / +Layout / +Wrapper; 'unknown' on the
|
|
307
|
+
* other roots when an entry call's component can't be resolved (picked at run time).
|
|
308
|
+
* runtime (D522): true on an app's root instance
|
|
309
|
+
*/
|
|
310
|
+
entry?: true | 'unknown'
|
|
311
|
+
/**
|
|
312
|
+
* static, Vike only (PLAN-10 A-9): on a +Page's component its route ('/orders/@id': from the
|
|
313
|
+
* filesystem, or the literal string a +route file / the config's `route` sets); null for a Route
|
|
314
|
+
* Function or a route the checker can't read (with `routeDynamic`) and for _error/+Page. On a
|
|
315
|
+
* +Layout's / +Wrapper's: the URL prefix of the directory it applies to
|
|
316
|
+
*/
|
|
317
|
+
route?: string | null
|
|
318
|
+
/**
|
|
319
|
+
* static, Vike only (PLAN-10 A-9): every route of a component several +Page files export (`route` is
|
|
320
|
+
* the first, in path order; null for a dynamic one or the error page); omitted otherwise
|
|
321
|
+
*/
|
|
322
|
+
routes?: (string | null)[]
|
|
323
|
+
/** static, Vike only (PLAN-10 A-9): the route is a Route Function or can't be read (route: null) */
|
|
324
|
+
routeDynamic?: true
|
|
325
|
+
/** runtime, PLAN-10 D522: the app (run()) it belongs to: its root's index among the live roots, oldest first */
|
|
326
|
+
app?: number
|
|
327
|
+
/** runtime, PLAN-10 D522: false on a hidden Switchable page and below it; omitted when shown */
|
|
328
|
+
shown?: false
|
|
227
329
|
actions: InspectAction[]
|
|
228
330
|
/** state keys (runtime: current state; static: initialState) */
|
|
229
331
|
stateKeys: string[]
|
|
@@ -231,14 +333,27 @@ export interface InspectComponent {
|
|
|
231
333
|
calculated: string[]
|
|
232
334
|
/** context fields this component provides (Component.context) */
|
|
233
335
|
contextProvides: string[]
|
|
234
|
-
/** context fields its view reads (static
|
|
336
|
+
/** context fields its view reads (static: the view source; runtime, D530: what its latest render read) */
|
|
235
337
|
contextConsumes?: string[] | null
|
|
338
|
+
/**
|
|
339
|
+
* static, PLAN-10 A-7: top-level state keys the view (and its child lenses, calculated and
|
|
340
|
+
* context functions) reads literally; omitted when none
|
|
341
|
+
*/
|
|
342
|
+
stateReads?: string[]
|
|
343
|
+
/** static, PLAN-10 A-7: stateReads may be incomplete (the state used whole somewhere) */
|
|
344
|
+
stateReadsPartial?: true
|
|
236
345
|
/** EVENTS types this component emits */
|
|
237
346
|
eventsEmitted: string[]
|
|
238
347
|
/** EVENTS types this component selects ('*' = EVENTS.select() without a type) */
|
|
239
348
|
eventsSelected: string[]
|
|
240
349
|
children: InspectChild[]
|
|
241
350
|
selectors: InspectSelector[]
|
|
351
|
+
/** static, PLAN-10 A-4: where the `context` static's entries are */
|
|
352
|
+
contextEntries?: Array<InspectLocation & { name: string }>
|
|
353
|
+
/** static, PLAN-10 A-4: the EVENTS emit and select sites of its intent and model */
|
|
354
|
+
eventSites?: Array<InspectLocation & { type: string; role: 'emit' | 'select' }>
|
|
355
|
+
/** static, PLAN-10 A-5: CHILD.select(Child) in its intent */
|
|
356
|
+
childSelects?: InspectChildSelect[]
|
|
242
357
|
/** PLAN-4 CT-1: the controls it renders (runtime: seen in its renders); omitted when none */
|
|
243
358
|
controls?: InspectControl[]
|
|
244
359
|
/** static only, PLAN-4 GS-2 (G-224): the element commands (ELEMENT sink) its model sends; omitted when none */
|
|
@@ -253,7 +368,7 @@ export interface InspectComponent {
|
|
|
253
368
|
}
|
|
254
369
|
|
|
255
370
|
/** PLAN-4 GS-2: an element command a model entry sends (static inspect, sygnal-check --graph) */
|
|
256
|
-
export interface InspectCommand {
|
|
371
|
+
export interface InspectCommand extends Partial<InspectLocation> {
|
|
257
372
|
/** the model entry that sends it */
|
|
258
373
|
action: string
|
|
259
374
|
/** the command's method (its first key) */
|
|
@@ -275,13 +390,15 @@ export interface InspectAgent {
|
|
|
275
390
|
description: string | null
|
|
276
391
|
/** the declaration has a read projection */
|
|
277
392
|
read: boolean
|
|
278
|
-
actions: Array<{ name: string; consequential: boolean; input: boolean }
|
|
393
|
+
actions: Array<{ name: string; consequential: boolean; input: boolean } & Partial<InspectLocation>>
|
|
394
|
+
/** the read-only `agent.queries` tools; omitted when none */
|
|
395
|
+
queries?: Array<{ name: string; input: boolean } & Partial<InspectLocation>>
|
|
279
396
|
/** spreads: what is listed may be incomplete */
|
|
280
397
|
partial?: true
|
|
281
398
|
}
|
|
282
399
|
|
|
283
400
|
/** PLAN-4 GS-7: a literal timer spec of a `timers` static (static inspect, sygnal-check --graph) */
|
|
284
|
-
export interface InspectTimer {
|
|
401
|
+
export interface InspectTimer extends Partial<InspectLocation> {
|
|
285
402
|
name: string
|
|
286
403
|
every?: number | null
|
|
287
404
|
after?: number | null
|
|
@@ -316,8 +433,12 @@ export interface InspectGraph {
|
|
|
316
433
|
/** which implementation produced the graph */
|
|
317
434
|
source?: 'runtime' | 'static'
|
|
318
435
|
components: InspectComponent[]
|
|
319
|
-
/**
|
|
320
|
-
|
|
436
|
+
/**
|
|
437
|
+
* EVENTS bus: type -> names of the components that emit / select it (each name once); emitterIds /
|
|
438
|
+
* selectorIds (PLAN-10 A-3, D522, D528): the id of every such component (static) or instance
|
|
439
|
+
* (runtime), not parallel to the names
|
|
440
|
+
*/
|
|
441
|
+
events: Record<string, { emitters: string[]; selectors: string[]; emitterIds?: string[]; selectorIds?: string[] }>
|
|
321
442
|
/** diagnostics not tied to a listed component */
|
|
322
443
|
diagnostics: InspectDiagnostic[]
|
|
323
444
|
/** runtime, PLAN-3 5-3: makeFetchDriver cache entries, by sink name (empty when no cache) */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 7.0 timing audit (SYG257, info): since 7.0 a model entry's STATE runs first and every other
|
|
3
|
+
* sink gets the state it committed (6.x gave them the state from before the action). For code
|
|
4
|
+
* written for 6.x, this reports each output sink that reads a top-level state field its action's
|
|
5
|
+
* STATE changed: it gets the new value now. Fix: read `props.previousState.<field>` where the old
|
|
6
|
+
* value is meant; a read meant to see the new value needs nothing. Opt-in, because in 7.0-native
|
|
7
|
+
* code reading the new value is the intent. Turn it on with any of:
|
|
8
|
+
* - configureTimingAudit(true) (this entry)
|
|
9
|
+
* - renderComponent(C, { timingAudit: true }) (tests)
|
|
10
|
+
* - sygnal({ diagnostics: { timingAudit: true } }) (sygnal/vite, dev)
|
|
11
|
+
* - globalThis.__SYGNAL_TIMING_AUDIT__ = true
|
|
12
|
+
* The flag lives on the core bridge (shared by every copy of this entry and renderComponent).
|
|
13
|
+
* Mechanism: the dev hook layer's wrapHandler (checks/next.ts) wraps each non-STATE handler and
|
|
14
|
+
* gives it the state through a Proxy that notes reads of changed top-level fields. Top-level
|
|
15
|
+
* fields only (a nested change shows as its top-level field changing); once per component name,
|
|
16
|
+
* action, sink and field. Nothing in production builds.
|
|
17
|
+
*/
|
|
18
|
+
import {bridge, devReport, once} from './shared'
|
|
19
|
+
|
|
20
|
+
/** Turn the timing audit on (true), off (false), or back to the default (globalThis.__SYGNAL_TIMING_AUDIT__). */
|
|
21
|
+
export function configureTimingAudit(on?: boolean): void {
|
|
22
|
+
const core = bridge()
|
|
23
|
+
if (core) core.timingAudit = on
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Whether the timing audit is on. */
|
|
27
|
+
export function isTimingAuditEnabled(): boolean {
|
|
28
|
+
const flag = bridge()?.timingAudit
|
|
29
|
+
return flag === undefined ? (globalThis as any).__SYGNAL_TIMING_AUDIT__ === true : flag === true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** handler `h` of `f`'s action `type`, sink `sink`, audited (f: the checks' instance facade) */
|
|
33
|
+
export const audited = (f: any, type: string, sink: string, h: any) => (s: any, d: any, nx: any, p: any, ...r: any[]) => {
|
|
34
|
+
const was = p?.previousState
|
|
35
|
+
if (!isTimingAuditEnabled() || !was || was === s || !s || typeof s != 'object' || typeof was != 'object' || Array.isArray(s)) return h(s, d, nx, p, ...r)
|
|
36
|
+
const view = new Proxy(s, {get(t: any, k: any, rcv: any) {
|
|
37
|
+
if (typeof k == 'string' && k in was && was[k] !== t[k] && once(`SYG257:${f.name}:${type}:${sink}:${k}`))
|
|
38
|
+
devReport('SYG257', {
|
|
39
|
+
component: f,
|
|
40
|
+
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)`,
|
|
41
|
+
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`,
|
|
42
|
+
data: {action: type, sink, field: k},
|
|
43
|
+
})
|
|
44
|
+
return Reflect.get(t, k, rcv)
|
|
45
|
+
}})
|
|
46
|
+
return h(view, d, nx, p, ...r)
|
|
47
|
+
}
|
|
@@ -152,6 +152,9 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
152
152
|
SYG127: 'Behavior collision or unresolvable uses entry',
|
|
153
153
|
SYG128: 'Duplicate control key',
|
|
154
154
|
SYG129: "CHILD.select() of a component this one doesn't render",
|
|
155
|
+
// PLAN-10 X-1 (D512): graph-wide checks, static only (sygnal-check)
|
|
156
|
+
SYG170: 'Context field read with no provider',
|
|
157
|
+
SYG171: 'PARENT sink no parent reads',
|
|
155
158
|
SYG112: 'Reply action has no model entry',
|
|
156
159
|
SYG130: 'href() names no route or leaves out a param',
|
|
157
160
|
SYG131: 'Route params the pattern does not use',
|
|
@@ -307,6 +310,8 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
307
310
|
SYG634: 'latest: true with a computed key',
|
|
308
311
|
// PLAN-3 5-5 (D88)
|
|
309
312
|
SYG635: 'Caching asked for without a queryCache',
|
|
313
|
+
// PLAN-11 U-1: progress requests (dev entry, fetch.ts)
|
|
314
|
+
SYG636: "progress that can't be reported as asked",
|
|
310
315
|
// PLAN-4 GS-2: element commands (reported by the dev entry, checks/elementCommands.ts)
|
|
311
316
|
SYG640: 'Element command target not found',
|
|
312
317
|
SYG641: 'Unknown element command',
|
|
@@ -366,6 +371,9 @@ export const CODE_TITLES: Record<string, string> = {
|
|
|
366
371
|
// PLAN-9 X-2: static only (sygnal-check rules/syg253-catalog.js)
|
|
367
372
|
SYG253: 'Misspelled catalog declaration key',
|
|
368
373
|
SYG254: 'Catalog descriptions or options overlap',
|
|
374
|
+
SYG256: 'Output guard rejects the updated state',
|
|
375
|
+
SYG257: 'Output reads a field its action changed (7.0 timing audit)',
|
|
376
|
+
SYG454: 'State binding changed without a new component identity',
|
|
369
377
|
// PLAN-9 H: chat client tools and ask (src/extra/ai/chat/behavior.ts, compose/ask.ts, compose/render.ts)
|
|
370
378
|
SYG160: 'Result part rendered outside a card',
|
|
371
379
|
SYG684: 'Client tool shadows an agent tool',
|
|
@@ -427,6 +435,9 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
427
435
|
SYG128: 'error',
|
|
428
436
|
// PLAN-4 4-G2: static only (sygnal-check); listed so the entry knows its title
|
|
429
437
|
SYG129: 'warn',
|
|
438
|
+
// PLAN-10 X-1: static only (sygnal-check)
|
|
439
|
+
SYG170: 'warn',
|
|
440
|
+
SYG171: 'warn',
|
|
430
441
|
SYG221: 'error',
|
|
431
442
|
// PLAN-4 GS-4 (state.ts)
|
|
432
443
|
SYG222: 'warn',
|
|
@@ -475,6 +486,8 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
475
486
|
SYG634: 'info',
|
|
476
487
|
// PLAN-3 5-5 (fetch.ts)
|
|
477
488
|
SYG635: 'warn',
|
|
489
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
490
|
+
SYG636: 'warn',
|
|
478
491
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
479
492
|
SYG640: 'warn',
|
|
480
493
|
SYG641: 'error',
|
|
@@ -591,6 +604,12 @@ export const DEV_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
|
591
604
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
592
605
|
SYG253: 'warn',
|
|
593
606
|
SYG254: 'info',
|
|
607
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
608
|
+
SYG256: 'warn',
|
|
609
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
610
|
+
SYG257: 'info',
|
|
611
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
612
|
+
SYG454: 'warn',
|
|
594
613
|
}
|
|
595
614
|
|
|
596
615
|
export function getCodeInfo(code: string): DiagnosticCodeInfo | undefined {
|