@wcstack/devtools 1.33.0 → 2.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/README.ja.md +3 -5
- package/README.md +3 -5
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +27 -33
- package/dist/index.esm.js +98 -155
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
/** グローバル registry のプロパティ名 */
|
|
12
12
|
const DEVTOOLS_HOOK_GLOBAL = "__WCSTACK_DEVTOOLS_HOOK__";
|
|
13
13
|
/** プロトコル版。additive change では上げない(protocol §2) */
|
|
14
|
-
const DEVTOOLS_PROTOCOL_VERSION =
|
|
14
|
+
const DEVTOOLS_PROTOCOL_VERSION = 2;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* protocol/registry.ts
|
|
@@ -210,36 +210,44 @@ function formatArgs(args) {
|
|
|
210
210
|
* 台帳はすべて devtools 側に置く(protocol 原則 2)。disconnect で
|
|
211
211
|
* sources / roster / wiring をクリアし、残留参照を持たない。
|
|
212
212
|
*/
|
|
213
|
-
/** 予約 state 名 prefix(protocol §5)。この prefix の要素・イベントは常に除外 */
|
|
214
|
-
const RESERVED_STATE_NAME_PREFIX = "wcs-devtools";
|
|
215
213
|
const DEFAULT_TIMELINE_CAPACITY = 500;
|
|
216
214
|
/** 台帳キーの区切り文字(state 名・パスに現れ得ない NUL)。
|
|
217
215
|
* エスケープ表記でなく生成式にしているのは、ツーリングがソース中の
|
|
218
216
|
* `エスケープ列` リテラルを生バイトへ壊す事故が実際に起きたため。 */
|
|
219
217
|
const NUL = String.fromCharCode(0);
|
|
220
|
-
function pathKeyOf(
|
|
221
|
-
return
|
|
218
|
+
function pathKeyOf(path) {
|
|
219
|
+
return path;
|
|
222
220
|
}
|
|
223
|
-
function tokenKeyOf(
|
|
224
|
-
return
|
|
221
|
+
function tokenKeyOf(kind, name) {
|
|
222
|
+
return kind + NUL + name;
|
|
225
223
|
}
|
|
226
|
-
/** 宣言(
|
|
227
|
-
function attachKeyOf(
|
|
228
|
-
return
|
|
224
|
+
/** 宣言(path+propName)単位の attach キー。NUL 区切りは tokenKeyOf と同じ理由 */
|
|
225
|
+
function attachKeyOf(path, propName) {
|
|
226
|
+
return path + NUL + propName;
|
|
227
|
+
}
|
|
228
|
+
/** roster 表示用のルート由来ラベル(v2: name 次元が無いのでルートで識別する) */
|
|
229
|
+
function rootLabelOf(rootNode) {
|
|
230
|
+
if (typeof ShadowRoot !== "undefined" && rootNode instanceof ShadowRoot) {
|
|
231
|
+
return `<${rootNode.host.tagName.toLowerCase()}>`;
|
|
232
|
+
}
|
|
233
|
+
if (rootNode.nodeType === 9 /* DOCUMENT_NODE */) {
|
|
234
|
+
return "document";
|
|
235
|
+
}
|
|
236
|
+
return rootNode.nodeName.toLowerCase();
|
|
229
237
|
}
|
|
230
238
|
/** token カバレッジ 1 行の判定(§4: 空撃ちのみ / 一部空撃ち / 正常 / 未発火) */
|
|
231
|
-
function tokenCoverageOf(
|
|
239
|
+
function tokenCoverageOf(kind, name, stat) {
|
|
232
240
|
if (stat === undefined) {
|
|
233
|
-
return {
|
|
241
|
+
return { kind, name, status: "never", count: 0, note: null };
|
|
234
242
|
}
|
|
235
243
|
if (stat.zeroSubscriberCount === stat.count) {
|
|
236
244
|
return {
|
|
237
|
-
|
|
245
|
+
kind, name, status: "emitted-unheard", count: stat.count,
|
|
238
246
|
note: `all ${stat.count} emit(s) had 0 subscribers`,
|
|
239
247
|
};
|
|
240
248
|
}
|
|
241
249
|
return {
|
|
242
|
-
|
|
250
|
+
kind, name, status: "emitted", count: stat.count,
|
|
243
251
|
note: stat.zeroSubscriberCount > 0
|
|
244
252
|
? `${stat.zeroSubscriberCount}/${stat.count} emit(s) had 0 subscribers`
|
|
245
253
|
: null,
|
|
@@ -247,7 +255,6 @@ function tokenCoverageOf(stateName, kind, name, stat) {
|
|
|
247
255
|
}
|
|
248
256
|
class DevtoolsCore {
|
|
249
257
|
_timelineCapacity;
|
|
250
|
-
_hiddenStateNames;
|
|
251
258
|
_removeListener = null;
|
|
252
259
|
_sources = new Map();
|
|
253
260
|
_roster = new Map();
|
|
@@ -259,9 +266,12 @@ class DevtoolsCore {
|
|
|
259
266
|
_changeListeners = new Set();
|
|
260
267
|
/** 観測開始時刻(connect 時の performance.now。未接続は null)。 */
|
|
261
268
|
_observingSince = null;
|
|
262
|
-
/** watch 発火回数(
|
|
269
|
+
/** watch 発火回数(path → count)。カバレッジの実測面。
|
|
270
|
+
* 既知の限界: `state:watch-fired` / `state:token-emit` の payload はツリー識別を
|
|
271
|
+
* 持たないため、複数ツリーが同名の watch パス / token 名を宣言するページでは
|
|
272
|
+
* 実測が合算される(ツリー別に分けるにはプロトコル追補が要る — slice 29 記録)。 */
|
|
263
273
|
_watchFiredCounts = new Map();
|
|
264
|
-
/** token emit 実測(
|
|
274
|
+
/** token emit 実測(kind + NUL + name → 回数と空撃ち回数)。上の限界注記と同じ。 */
|
|
265
275
|
_tokenEmitCounts = new Map();
|
|
266
276
|
/** 観測開始以降に一度でも attach を観測した宣言キー(attachKeyOf)。
|
|
267
277
|
* live 配線台帳は WeakRef pruning / binding-removed で縮むため、binding
|
|
@@ -270,7 +280,6 @@ class DevtoolsCore {
|
|
|
270
280
|
_everAttachedKeys = new Set();
|
|
271
281
|
constructor(options) {
|
|
272
282
|
this._timelineCapacity = options?.timelineCapacity ?? DEFAULT_TIMELINE_CAPACITY;
|
|
273
|
-
this._hiddenStateNames = new Set(options?.hiddenStateNames ?? []);
|
|
274
283
|
}
|
|
275
284
|
get connected() {
|
|
276
285
|
return this._removeListener !== null;
|
|
@@ -281,13 +290,6 @@ class DevtoolsCore {
|
|
|
281
290
|
set paused(value) {
|
|
282
291
|
this._paused = value;
|
|
283
292
|
}
|
|
284
|
-
/** 表示から除外する state 名か(予約 prefix + hiddenStateNames、protocol §5) */
|
|
285
|
-
isHiddenStateName(name) {
|
|
286
|
-
if (name === null) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
return name.startsWith(RESERVED_STATE_NAME_PREFIX) || this._hiddenStateNames.has(name);
|
|
290
|
-
}
|
|
291
293
|
connect() {
|
|
292
294
|
if (this._removeListener !== null) {
|
|
293
295
|
return;
|
|
@@ -367,13 +369,22 @@ class DevtoolsCore {
|
|
|
367
369
|
this._timeline = [];
|
|
368
370
|
this._notify("timeline");
|
|
369
371
|
}
|
|
370
|
-
/**
|
|
371
|
-
|
|
372
|
-
|
|
372
|
+
/**
|
|
373
|
+
* 指定パスに束縛された配線(生存している binding のみ)。
|
|
374
|
+
* stateElement を渡すとそのツリーの配線だけに絞る(v2 は名前次元が無いため、
|
|
375
|
+
* 複数ツリーの同名パスは要素同一性で区別する)。stateElement を持たない
|
|
376
|
+
* 旧 payload 由来の entry は絞り込みでも残す(欠測を欠落にしない)。
|
|
377
|
+
*/
|
|
378
|
+
getWiringForPath(path, stateElement) {
|
|
379
|
+
const set = this._wiringByPathKey.get(pathKeyOf(path));
|
|
373
380
|
if (set === undefined) {
|
|
374
381
|
return [];
|
|
375
382
|
}
|
|
376
|
-
|
|
383
|
+
const alive = this._collectAlive(set);
|
|
384
|
+
if (stateElement === undefined) {
|
|
385
|
+
return alive;
|
|
386
|
+
}
|
|
387
|
+
return alive.filter((entry) => entry.stateElementRef === null || entry.stateElementRef.deref() === stateElement);
|
|
377
388
|
}
|
|
378
389
|
/** 全配線のスナップショット(生存している binding のみ) */
|
|
379
390
|
getAllWiring() {
|
|
@@ -421,10 +432,7 @@ class DevtoolsCore {
|
|
|
421
432
|
}
|
|
422
433
|
for (const root of roots) {
|
|
423
434
|
for (const declared of source.getDeclaredBindings(root)) {
|
|
424
|
-
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
const key = [declared.stateName, declared.statePathName, declared.propName,
|
|
435
|
+
const key = [declared.statePathName, declared.propName,
|
|
428
436
|
declared.bindingType, declared.origin, declared.raw].join(NUL);
|
|
429
437
|
if (seen.has(key)) {
|
|
430
438
|
continue;
|
|
@@ -447,9 +455,9 @@ class DevtoolsCore {
|
|
|
447
455
|
const summary = entry.summary;
|
|
448
456
|
// --- watch: 宣言(watchPaths)× 実測(watch-fired 台帳) ---
|
|
449
457
|
for (const path of summary.watchPaths ?? []) {
|
|
450
|
-
const count = this._watchFiredCounts.get(pathKeyOf(
|
|
458
|
+
const count = this._watchFiredCounts.get(pathKeyOf(path)) ?? 0;
|
|
451
459
|
if (count > 0) {
|
|
452
|
-
out.push({
|
|
460
|
+
out.push({ kind: "watch", name: path, status: "fired", count, note: null });
|
|
453
461
|
continue;
|
|
454
462
|
}
|
|
455
463
|
// ワイルドカード行 watch に**リスト書き込み**が届く前提 = 各 `.*` 階層の
|
|
@@ -473,23 +481,23 @@ class DevtoolsCore {
|
|
|
473
481
|
}
|
|
474
482
|
if (missingList !== null) {
|
|
475
483
|
out.push({
|
|
476
|
-
|
|
484
|
+
kind: "watch", name: path, status: "prerequisite-missing", count: 0,
|
|
477
485
|
note: keyedKnown
|
|
478
486
|
? `list "${missingList}" has no for binding and no $listKeys declaration — list writes never reach its rows (only explicit-index writes can fire this watch)`
|
|
479
487
|
: `no for binding observed for list "${missingList}" (a $listKeys declaration would still let list writes fire it)`,
|
|
480
488
|
});
|
|
481
489
|
continue;
|
|
482
490
|
}
|
|
483
|
-
out.push({
|
|
491
|
+
out.push({ kind: "watch", name: path, status: "never", count: 0, note: null });
|
|
484
492
|
}
|
|
485
493
|
// --- token: 宣言(commandTokenNames / eventTokenNames)× 実測(emit 台帳)。
|
|
486
494
|
// subscriberCount 0 = 空撃ち(§4): 全 emit が空撃ちなら emitted-unheard として
|
|
487
495
|
// 警告する(emit 側だけ配線されて受け手が一つも居ない構成ミスの検出)。 ---
|
|
488
496
|
for (const name of summary.commandTokenNames) {
|
|
489
|
-
out.push(tokenCoverageOf(
|
|
497
|
+
out.push(tokenCoverageOf("command", name, this._tokenEmitCounts.get(tokenKeyOf("command", name))));
|
|
490
498
|
}
|
|
491
499
|
for (const name of summary.eventTokenNames) {
|
|
492
|
-
out.push(tokenCoverageOf(
|
|
500
|
+
out.push(tokenCoverageOf("eventToken", name, this._tokenEmitCounts.get(tokenKeyOf("event", name))));
|
|
493
501
|
}
|
|
494
502
|
}
|
|
495
503
|
// --- binding: canonical declared(宣言)× live 配線台帳(実測)。canonical が
|
|
@@ -504,7 +512,7 @@ class DevtoolsCore {
|
|
|
504
512
|
continue;
|
|
505
513
|
if (decl.propName.startsWith("eventToken."))
|
|
506
514
|
continue;
|
|
507
|
-
const key = attachKeyOf(decl.
|
|
515
|
+
const key = attachKeyOf(decl.statePathName, decl.propName);
|
|
508
516
|
if (seen.has(key))
|
|
509
517
|
continue;
|
|
510
518
|
seen.add(key);
|
|
@@ -512,7 +520,7 @@ class DevtoolsCore {
|
|
|
512
520
|
// 破棄や GC(WeakRef pruning)で縮むため、瞬間値だと逆戻りする。
|
|
513
521
|
const attached = this._everAttachedKeys.has(key);
|
|
514
522
|
out.push({
|
|
515
|
-
|
|
523
|
+
kind: "binding",
|
|
516
524
|
name: `${decl.propName} ← ${decl.statePathName}`,
|
|
517
525
|
status: attached ? "attached" : "never-attached",
|
|
518
526
|
count: attached ? 1 : 0,
|
|
@@ -528,21 +536,21 @@ class DevtoolsCore {
|
|
|
528
536
|
if (source === undefined || typeof source.keys !== "function") {
|
|
529
537
|
return [];
|
|
530
538
|
}
|
|
531
|
-
return source.keys(entry.
|
|
539
|
+
return source.keys(entry.rootNode);
|
|
532
540
|
}
|
|
533
541
|
readValue(entry, path, indexes) {
|
|
534
542
|
const source = this._sources.get(entry.sourceId);
|
|
535
543
|
if (source === undefined) {
|
|
536
544
|
return undefined;
|
|
537
545
|
}
|
|
538
|
-
return source.read(entry.
|
|
546
|
+
return source.read(entry.rootNode, path, indexes);
|
|
539
547
|
}
|
|
540
548
|
writeValue(entry, path, value, indexes) {
|
|
541
549
|
const source = this._sources.get(entry.sourceId);
|
|
542
550
|
if (source === undefined) {
|
|
543
551
|
return;
|
|
544
552
|
}
|
|
545
|
-
source.write(entry.
|
|
553
|
+
source.write(entry.rootNode, path, value, indexes);
|
|
546
554
|
}
|
|
547
555
|
// --- internal ---
|
|
548
556
|
_notify(kind) {
|
|
@@ -552,13 +560,14 @@ class DevtoolsCore {
|
|
|
552
560
|
}
|
|
553
561
|
_refreshRosterOf(source) {
|
|
554
562
|
const entries = [];
|
|
563
|
+
const labelCounts = new Map();
|
|
555
564
|
for (const summary of source.getStateElements()) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
565
|
+
const base = rootLabelOf(summary.rootNode);
|
|
566
|
+
const seen = (labelCounts.get(base) ?? 0) + 1;
|
|
567
|
+
labelCounts.set(base, seen);
|
|
559
568
|
entries.push({
|
|
560
569
|
sourceId: source.id,
|
|
561
|
-
|
|
570
|
+
label: seen === 1 ? base : `${base}#${seen}`,
|
|
562
571
|
rootNode: summary.rootNode,
|
|
563
572
|
summary,
|
|
564
573
|
});
|
|
@@ -600,9 +609,6 @@ class DevtoolsCore {
|
|
|
600
609
|
_ingest(sourceId, event) {
|
|
601
610
|
switch (event.type) {
|
|
602
611
|
case "state:element-registered": {
|
|
603
|
-
if (this.isHiddenStateName(event.name)) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
612
|
const source = this._sources.get(sourceId);
|
|
607
613
|
if (source !== undefined) {
|
|
608
614
|
this._refreshRosterOf(source);
|
|
@@ -611,17 +617,13 @@ class DevtoolsCore {
|
|
|
611
617
|
this._appendTimeline({
|
|
612
618
|
sourceId,
|
|
613
619
|
kind: "element-registered",
|
|
614
|
-
|
|
615
|
-
label: event.name,
|
|
620
|
+
label: rootLabelOf(event.rootNode),
|
|
616
621
|
detail: "",
|
|
617
622
|
subscriberCount: null,
|
|
618
623
|
});
|
|
619
624
|
return;
|
|
620
625
|
}
|
|
621
626
|
case "state:element-unregistered": {
|
|
622
|
-
if (this.isHiddenStateName(event.name)) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
627
|
const source = this._sources.get(sourceId);
|
|
626
628
|
if (source !== undefined) {
|
|
627
629
|
this._refreshRosterOf(source);
|
|
@@ -630,25 +632,19 @@ class DevtoolsCore {
|
|
|
630
632
|
this._appendTimeline({
|
|
631
633
|
sourceId,
|
|
632
634
|
kind: "element-unregistered",
|
|
633
|
-
|
|
634
|
-
label: event.name,
|
|
635
|
+
label: rootLabelOf(event.rootNode),
|
|
635
636
|
detail: "",
|
|
636
637
|
subscriberCount: null,
|
|
637
638
|
});
|
|
638
639
|
return;
|
|
639
640
|
}
|
|
640
641
|
case "state:write": {
|
|
641
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
642
|
-
if (this.isHiddenStateName(stateName)) {
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
642
|
const detail = event.hasOldValue
|
|
646
643
|
? `${formatValue(event.value)} (was ${formatValue(event.oldValue)})`
|
|
647
644
|
: formatValue(event.value);
|
|
648
645
|
this._appendTimeline({
|
|
649
646
|
sourceId,
|
|
650
647
|
kind: "write",
|
|
651
|
-
stateName,
|
|
652
648
|
label: this._labelOf(event.absoluteAddress),
|
|
653
649
|
detail,
|
|
654
650
|
subscriberCount: null,
|
|
@@ -659,9 +655,6 @@ class DevtoolsCore {
|
|
|
659
655
|
const labels = [];
|
|
660
656
|
let total = 0;
|
|
661
657
|
for (const address of event.addresses) {
|
|
662
|
-
if (this.isHiddenStateName(address.absolutePathInfo.stateName)) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
658
|
total++;
|
|
666
659
|
if (labels.length < 3) {
|
|
667
660
|
labels.push(this._labelOf(address));
|
|
@@ -674,7 +667,6 @@ class DevtoolsCore {
|
|
|
674
667
|
this._appendTimeline({
|
|
675
668
|
sourceId,
|
|
676
669
|
kind: "batch",
|
|
677
|
-
stateName: null,
|
|
678
670
|
label: `${total} address${total === 1 ? "" : "es"}`,
|
|
679
671
|
detail: `${labels.join(", ")}${rest}`,
|
|
680
672
|
subscriberCount: null,
|
|
@@ -682,29 +674,28 @@ class DevtoolsCore {
|
|
|
682
674
|
return;
|
|
683
675
|
}
|
|
684
676
|
case "state:binding-added": {
|
|
685
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
686
|
-
if (this.isHiddenStateName(stateName)) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
677
|
const path = event.absoluteAddress.absolutePathInfo.pathInfo.path;
|
|
690
|
-
const key = pathKeyOf(
|
|
678
|
+
const key = pathKeyOf(path);
|
|
691
679
|
let set = this._wiringByPathKey.get(key);
|
|
692
680
|
if (set === undefined) {
|
|
693
681
|
set = new Set();
|
|
694
682
|
this._wiringByPathKey.set(key, set);
|
|
695
683
|
}
|
|
684
|
+
const stateElement = event.absoluteAddress.absolutePathInfo.stateElement;
|
|
696
685
|
const entry = {
|
|
697
686
|
sourceId,
|
|
698
|
-
stateName,
|
|
699
687
|
path,
|
|
700
688
|
propName: event.binding.propName,
|
|
701
689
|
bindingType: event.binding.bindingType,
|
|
702
690
|
bindingRef: new WeakRef(event.binding),
|
|
691
|
+
stateElementRef: typeof stateElement === "object" && stateElement !== null
|
|
692
|
+
? new WeakRef(stateElement)
|
|
693
|
+
: null,
|
|
703
694
|
};
|
|
704
695
|
set.add(entry);
|
|
705
696
|
this._wiringEntryByBinding.set(event.binding, entry);
|
|
706
697
|
// カバレッジの実測面(B2): attach の観測を ever 台帳へ積む(remove では消さない)
|
|
707
|
-
this._everAttachedKeys.add(attachKeyOf(
|
|
698
|
+
this._everAttachedKeys.add(attachKeyOf(path, event.binding.propName));
|
|
708
699
|
this._notify("wiring");
|
|
709
700
|
this._notify("coverage");
|
|
710
701
|
return;
|
|
@@ -715,7 +706,7 @@ class DevtoolsCore {
|
|
|
715
706
|
return;
|
|
716
707
|
}
|
|
717
708
|
this._wiringEntryByBinding.delete(event.binding);
|
|
718
|
-
const key = pathKeyOf(entry.
|
|
709
|
+
const key = pathKeyOf(entry.path);
|
|
719
710
|
const set = this._wiringByPathKey.get(key);
|
|
720
711
|
if (set !== undefined) {
|
|
721
712
|
set.delete(entry);
|
|
@@ -727,41 +718,33 @@ class DevtoolsCore {
|
|
|
727
718
|
return;
|
|
728
719
|
}
|
|
729
720
|
case "state:binding-cleared": {
|
|
730
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
731
721
|
const path = event.absoluteAddress.absolutePathInfo.pathInfo.path;
|
|
732
|
-
const key = pathKeyOf(
|
|
722
|
+
const key = pathKeyOf(path);
|
|
733
723
|
if (this._wiringByPathKey.delete(key)) {
|
|
734
724
|
this._notify("wiring");
|
|
735
725
|
}
|
|
736
726
|
return;
|
|
737
727
|
}
|
|
738
728
|
case "state:token-emit": {
|
|
739
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
729
|
// カバレッジの実測面: emit 回数と空撃ち回数を台帳に積む(§4)。
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
stat.
|
|
755
|
-
if (event.subscriberCount === 0) {
|
|
756
|
-
stat.zeroSubscriberCount += 1;
|
|
757
|
-
}
|
|
730
|
+
const emitKey = tokenKeyOf(event.kind, event.tokenName);
|
|
731
|
+
const stat = this._tokenEmitCounts.get(emitKey);
|
|
732
|
+
if (stat === undefined) {
|
|
733
|
+
this._tokenEmitCounts.set(emitKey, {
|
|
734
|
+
count: 1,
|
|
735
|
+
zeroSubscriberCount: event.subscriberCount === 0 ? 1 : 0,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
stat.count += 1;
|
|
740
|
+
if (event.subscriberCount === 0) {
|
|
741
|
+
stat.zeroSubscriberCount += 1;
|
|
758
742
|
}
|
|
759
|
-
this._notify("coverage");
|
|
760
743
|
}
|
|
744
|
+
this._notify("coverage");
|
|
761
745
|
this._appendTimeline({
|
|
762
746
|
sourceId,
|
|
763
747
|
kind: event.kind,
|
|
764
|
-
stateName: event.stateName,
|
|
765
748
|
label: event.tokenName,
|
|
766
749
|
detail: formatArgs(event.args),
|
|
767
750
|
subscriberCount: event.subscriberCount,
|
|
@@ -769,26 +752,19 @@ class DevtoolsCore {
|
|
|
769
752
|
return;
|
|
770
753
|
}
|
|
771
754
|
case "state:watch-fired": {
|
|
772
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
755
|
// timeline 行にはしない(発火は高頻度になりうる活動でありカバレッジが受け皿)。
|
|
776
|
-
const firedKey = pathKeyOf(event.
|
|
756
|
+
const firedKey = pathKeyOf(event.path);
|
|
777
757
|
this._watchFiredCounts.set(firedKey, (this._watchFiredCounts.get(firedKey) ?? 0) + 1);
|
|
778
758
|
this._notify("coverage");
|
|
779
759
|
return;
|
|
780
760
|
}
|
|
781
761
|
case "state:watch-error": {
|
|
782
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
762
|
// ランタイム側は例外を握って drain を守るため、ここに出ないと失敗が
|
|
786
763
|
// どこにも現れない(console を見ていない限り)。phase を detail の先頭に
|
|
787
764
|
// 置くのは、getter の評価失敗とハンドラの失敗で直し方が違うため。
|
|
788
765
|
this._appendTimeline({
|
|
789
766
|
sourceId,
|
|
790
767
|
kind: "watch-error",
|
|
791
|
-
stateName: event.stateName,
|
|
792
768
|
label: event.path,
|
|
793
769
|
detail: `${event.phase}: ${formatError(event.error)}`,
|
|
794
770
|
subscriberCount: null,
|
|
@@ -799,9 +775,7 @@ class DevtoolsCore {
|
|
|
799
775
|
this._appendTimeline({
|
|
800
776
|
sourceId,
|
|
801
777
|
kind: "watch-chain-limit",
|
|
802
|
-
|
|
803
|
-
// 打ち切りはバッチ単位で state 名を持たない(複数 state のアドレスが
|
|
804
|
-
// 載りうる)ため、hidden 判定はここでは行わない。
|
|
778
|
+
// 打ち切りはバッチ単位(複数ツリーのアドレスが載りうる)— 行の主語は深さ上限
|
|
805
779
|
label: `depth > ${event.maxDepth}`,
|
|
806
780
|
detail: event.paths.join(", "),
|
|
807
781
|
subscriberCount: null,
|
|
@@ -809,15 +783,11 @@ class DevtoolsCore {
|
|
|
809
783
|
return;
|
|
810
784
|
}
|
|
811
785
|
case "state:path-unresolved": {
|
|
812
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
786
|
// ランタイムは warn で続行する(既存ページを止めないため)。lint を
|
|
816
787
|
// 走らせていない相手には、ここが「配線が死んでいる」唯一の可視面になる。
|
|
817
788
|
this._appendTimeline({
|
|
818
789
|
sourceId,
|
|
819
790
|
kind: "path-unresolved",
|
|
820
|
-
stateName: event.stateName,
|
|
821
791
|
label: event.path,
|
|
822
792
|
detail: `${event.source}: "${event.missingSegment}" is not declared`,
|
|
823
793
|
subscriberCount: null,
|
|
@@ -825,15 +795,11 @@ class DevtoolsCore {
|
|
|
825
795
|
return;
|
|
826
796
|
}
|
|
827
797
|
case "state:binding-apply-error": {
|
|
828
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
798
|
// 隔離された適用失敗。bindingType を detail 先頭に置くのは、構造
|
|
832
799
|
// (for / if)の失敗と値の失敗で影響範囲が違うため。
|
|
833
800
|
this._appendTimeline({
|
|
834
801
|
sourceId,
|
|
835
802
|
kind: "binding-apply-error",
|
|
836
|
-
stateName: event.stateName,
|
|
837
803
|
label: event.path,
|
|
838
804
|
detail: `${event.bindingType}: ${formatError(event.error)}`,
|
|
839
805
|
subscriberCount: null,
|
|
@@ -841,12 +807,10 @@ class DevtoolsCore {
|
|
|
841
807
|
return;
|
|
842
808
|
}
|
|
843
809
|
case "propagation:suppressed": {
|
|
844
|
-
// two-way
|
|
845
|
-
// ため hidden 判定はここでは行わない。
|
|
810
|
+
// two-way エコーの辺単位抑止(主語は node + member — パスではない)
|
|
846
811
|
this._appendTimeline({
|
|
847
812
|
sourceId,
|
|
848
813
|
kind: "propagation-suppressed",
|
|
849
|
-
stateName: null,
|
|
850
814
|
label: event.member,
|
|
851
815
|
detail: `${event.reason} (tx ${event.transactionId}, edge ${event.edgeId})`,
|
|
852
816
|
subscriberCount: null,
|
|
@@ -854,14 +818,9 @@ class DevtoolsCore {
|
|
|
854
818
|
return;
|
|
855
819
|
}
|
|
856
820
|
case "propagation:coalesced": {
|
|
857
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
858
|
-
if (this.isHiddenStateName(stateName)) {
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
821
|
this._appendTimeline({
|
|
862
822
|
sourceId,
|
|
863
823
|
kind: "propagation-coalesced",
|
|
864
|
-
stateName,
|
|
865
824
|
label: this._labelOf(event.absoluteAddress),
|
|
866
825
|
detail: `tx ${event.droppedTransactionId} dropped (winner tx ${event.winnerTransactionId})`,
|
|
867
826
|
subscriberCount: null,
|
|
@@ -869,14 +828,9 @@ class DevtoolsCore {
|
|
|
869
828
|
return;
|
|
870
829
|
}
|
|
871
830
|
case "propagation:hop-limit": {
|
|
872
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
873
|
-
if (this.isHiddenStateName(stateName)) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
831
|
this._appendTimeline({
|
|
877
832
|
sourceId,
|
|
878
833
|
kind: "propagation-hop-limit",
|
|
879
|
-
stateName,
|
|
880
834
|
label: this._labelOf(event.absoluteAddress),
|
|
881
835
|
detail: `hop ${event.hop} (tx ${event.transactionId})`,
|
|
882
836
|
subscriberCount: null,
|
|
@@ -894,7 +848,6 @@ class DevtoolsCore {
|
|
|
894
848
|
this._appendTimeline({
|
|
895
849
|
sourceId,
|
|
896
850
|
kind: "contract-drift",
|
|
897
|
-
stateName: null,
|
|
898
851
|
label: event.tag,
|
|
899
852
|
detail: `${event.reason}${memberPart}${eventPart}`,
|
|
900
853
|
subscriberCount: null,
|
|
@@ -922,7 +875,7 @@ class DevtoolsCore {
|
|
|
922
875
|
* 「宣言レベルの配線ビュー」を組む。ライブ台帳と違い binding 実体・
|
|
923
876
|
* 接続状態は分からない(UI では "declared" バッジで区別する)。
|
|
924
877
|
*
|
|
925
|
-
* パースは表示目的の簡易版(`prop[#mod]: path[
|
|
878
|
+
* パースは表示目的の簡易版(`prop[#mod]: path[|filters]` を
|
|
926
879
|
* `;` 区切りで分解するだけ)。正確なセマンティクスの正本は
|
|
927
880
|
* @wcstack/state の bindTextParser であり、ここでは追随しない。
|
|
928
881
|
*/
|
|
@@ -943,18 +896,13 @@ function parseEntry(element, raw, origin) {
|
|
|
943
896
|
return null;
|
|
944
897
|
}
|
|
945
898
|
const [pathPart, ...filterParts] = rhs.split("|").map((part) => part.trim());
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const
|
|
949
|
-
if (at > 0) {
|
|
950
|
-
path = pathPart.slice(0, at).trim();
|
|
951
|
-
stateName = pathPart.slice(at + 1).trim();
|
|
952
|
-
}
|
|
899
|
+
// v2: `@name` セレクタは撤去済み(ランタイムは parse error にする)。
|
|
900
|
+
// 簡易パーサはパスをそのまま流す — 壊れた宣言の可視化はランタイム側の役目。
|
|
901
|
+
const path = pathPart;
|
|
953
902
|
return {
|
|
954
903
|
element,
|
|
955
904
|
propName,
|
|
956
905
|
path,
|
|
957
|
-
stateName,
|
|
958
906
|
filters: filterParts.filter((part) => part.length > 0),
|
|
959
907
|
origin,
|
|
960
908
|
raw: trimmed,
|
|
@@ -1178,13 +1126,8 @@ class WcsDevtools extends HTMLElement {
|
|
|
1178
1126
|
this._buildShadow();
|
|
1179
1127
|
}
|
|
1180
1128
|
const capacity = Number(this.getAttribute("buffer") ?? "");
|
|
1181
|
-
const hidden = (this.getAttribute("hidden-states") ?? "")
|
|
1182
|
-
.split(",")
|
|
1183
|
-
.map((name) => name.trim())
|
|
1184
|
-
.filter((name) => name.length > 0);
|
|
1185
1129
|
this._core = new DevtoolsCore({
|
|
1186
1130
|
timelineCapacity: Number.isFinite(capacity) && capacity > 0 ? capacity : undefined,
|
|
1187
|
-
hiddenStateNames: hidden,
|
|
1188
1131
|
});
|
|
1189
1132
|
this._removeCoreListener = this._core.onChange((kind) => {
|
|
1190
1133
|
if (kind === "roster" || kind === "sources") {
|
|
@@ -1461,7 +1404,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1461
1404
|
}
|
|
1462
1405
|
}
|
|
1463
1406
|
_rosterKey(entry) {
|
|
1464
|
-
return `${entry.sourceId}:${entry.
|
|
1407
|
+
return `${entry.sourceId}:${entry.label}`;
|
|
1465
1408
|
}
|
|
1466
1409
|
_selectedRoster() {
|
|
1467
1410
|
const core = this._core;
|
|
@@ -1481,7 +1424,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1481
1424
|
select.replaceChildren(...roster.map((entry) => {
|
|
1482
1425
|
const option = document.createElement("option");
|
|
1483
1426
|
option.value = this._rosterKey(entry);
|
|
1484
|
-
option.textContent = `${entry.
|
|
1427
|
+
option.textContent = `${entry.label} (${entry.sourceId.slice(0, 12)})`;
|
|
1485
1428
|
option.selected = selected !== null && this._rosterKey(entry) === this._rosterKey(selected);
|
|
1486
1429
|
return option;
|
|
1487
1430
|
}));
|
|
@@ -1632,8 +1575,9 @@ class WcsDevtools extends HTMLElement {
|
|
|
1632
1575
|
}
|
|
1633
1576
|
else if (this._selectedPath !== null) {
|
|
1634
1577
|
const selected = this._selectedRoster();
|
|
1578
|
+
// 選択中のツリーでスコープ(複数ツリーの同名パスを混ぜない)
|
|
1635
1579
|
entries =
|
|
1636
|
-
selected !== null ? core.getWiringForPath(
|
|
1580
|
+
selected !== null ? core.getWiringForPath(this._selectedPath, selected.summary.element) : [];
|
|
1637
1581
|
contextLabel = this._selectedPath;
|
|
1638
1582
|
}
|
|
1639
1583
|
else {
|
|
@@ -1726,7 +1670,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1726
1670
|
}
|
|
1727
1671
|
const label = document.createElement("span");
|
|
1728
1672
|
label.className = "path";
|
|
1729
|
-
label.textContent = ` ${entry.name}
|
|
1673
|
+
label.textContent = ` ${entry.name} `;
|
|
1730
1674
|
row.append(kind, document.createTextNode(" "), label, status);
|
|
1731
1675
|
if (entry.note !== null) {
|
|
1732
1676
|
// note はホバー頼み(title)にせず常時表示する — 前提未成立の理由は
|
|
@@ -1752,7 +1696,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1752
1696
|
const path = document.createElement("span");
|
|
1753
1697
|
path.className = "path";
|
|
1754
1698
|
const filters = entry.outFilters.map((f) => ` | ${f.filterName}(${f.args.join(",")})`).join("");
|
|
1755
|
-
path.textContent = `${entry.statePathName}
|
|
1699
|
+
path.textContent = `${entry.statePathName}${filters}`;
|
|
1756
1700
|
row.append(type, document.createTextNode(" "), prop, document.createTextNode(" ← "), path);
|
|
1757
1701
|
if (entry.node !== null) {
|
|
1758
1702
|
const target = entry.node;
|
|
@@ -1781,7 +1725,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1781
1725
|
const arrow = document.createTextNode(" ← ");
|
|
1782
1726
|
const path = document.createElement("span");
|
|
1783
1727
|
path.className = "path";
|
|
1784
|
-
path.textContent =
|
|
1728
|
+
path.textContent = entry.path;
|
|
1785
1729
|
const type = document.createElement("span");
|
|
1786
1730
|
type.className = "badge-tag";
|
|
1787
1731
|
type.textContent = entry.bindingType;
|
|
@@ -1805,7 +1749,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1805
1749
|
prop.textContent = entry.propName;
|
|
1806
1750
|
const path = document.createElement("span");
|
|
1807
1751
|
path.className = "path";
|
|
1808
|
-
path.textContent =
|
|
1752
|
+
path.textContent = entry.path;
|
|
1809
1753
|
row.append(type, document.createTextNode(" "), prop, document.createTextNode(" ← "), path);
|
|
1810
1754
|
row.addEventListener("click", () => {
|
|
1811
1755
|
this._highlightNodes([entry.element]);
|
|
@@ -1870,8 +1814,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1870
1814
|
}
|
|
1871
1815
|
const label = document.createElement("span");
|
|
1872
1816
|
label.className = "label";
|
|
1873
|
-
|
|
1874
|
-
label.textContent = ` ${entry.label}${stateName} `;
|
|
1817
|
+
label.textContent = ` ${entry.label} `;
|
|
1875
1818
|
const detail = document.createElement("span");
|
|
1876
1819
|
detail.className = "detail";
|
|
1877
1820
|
detail.textContent = entry.detail;
|
|
@@ -1888,7 +1831,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1888
1831
|
_highlightPath(entry, path) {
|
|
1889
1832
|
const core = this._core;
|
|
1890
1833
|
const nodes = [];
|
|
1891
|
-
for (const wiring of core.getWiringForPath(entry.
|
|
1834
|
+
for (const wiring of core.getWiringForPath(path, entry.summary.element)) {
|
|
1892
1835
|
const binding = wiring.bindingRef.deref();
|
|
1893
1836
|
if (binding !== undefined) {
|
|
1894
1837
|
nodes.push(binding.node, binding.replaceNode);
|
|
@@ -1954,5 +1897,5 @@ function bootstrapDevtools() {
|
|
|
1954
1897
|
}
|
|
1955
1898
|
}
|
|
1956
1899
|
|
|
1957
|
-
export { DEVTOOLS_HOOK_GLOBAL, DEVTOOLS_PROTOCOL_VERSION, DevtoolsCore,
|
|
1900
|
+
export { DEVTOOLS_HOOK_GLOBAL, DEVTOOLS_PROTOCOL_VERSION, DevtoolsCore, WcsDevtools, bootstrapDevtools, formatArgs, formatValue, getOrCreateHookRegistry, scanDeclaredBindings };
|
|
1958
1901
|
//# sourceMappingURL=index.esm.js.map
|