@wcstack/devtools 1.33.0 → 2.1.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 +111 -36
- package/dist/index.esm.js +248 -165
- 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,10 +266,14 @@ class DevtoolsCore {
|
|
|
259
266
|
_changeListeners = new Set();
|
|
260
267
|
/** 観測開始時刻(connect 時の performance.now。未接続は null)。 */
|
|
261
268
|
_observingSince = null;
|
|
262
|
-
/** watch
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
/** watch 発火実測(path → ツリー別区分列)。カバレッジの実測面。
|
|
270
|
+
* protocol v2 追補で payload がツリー識別(stateElement)を持つようになり、
|
|
271
|
+
* 複数ツリーが同名 watch パスを宣言するページでもツリー別に数えられる。
|
|
272
|
+
* 識別の無い旧 payload は null 区分に積み、どのツリーの照会にも合算で残す
|
|
273
|
+
* (slice 29 non-blocking 記録の解消)。 */
|
|
274
|
+
_watchFiredStats = new Map();
|
|
275
|
+
/** token emit 実測(kind + NUL + name → ツリー別区分列)。上の追補注記と同じ。 */
|
|
276
|
+
_tokenEmitStats = new Map();
|
|
266
277
|
/** 観測開始以降に一度でも attach を観測した宣言キー(attachKeyOf)。
|
|
267
278
|
* live 配線台帳は WeakRef pruning / binding-removed で縮むため、binding
|
|
268
279
|
* カバレッジの attached 判定はこちらで行う(瞬間値で判定すると行の
|
|
@@ -270,7 +281,6 @@ class DevtoolsCore {
|
|
|
270
281
|
_everAttachedKeys = new Set();
|
|
271
282
|
constructor(options) {
|
|
272
283
|
this._timelineCapacity = options?.timelineCapacity ?? DEFAULT_TIMELINE_CAPACITY;
|
|
273
|
-
this._hiddenStateNames = new Set(options?.hiddenStateNames ?? []);
|
|
274
284
|
}
|
|
275
285
|
get connected() {
|
|
276
286
|
return this._removeListener !== null;
|
|
@@ -281,13 +291,6 @@ class DevtoolsCore {
|
|
|
281
291
|
set paused(value) {
|
|
282
292
|
this._paused = value;
|
|
283
293
|
}
|
|
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
294
|
connect() {
|
|
292
295
|
if (this._removeListener !== null) {
|
|
293
296
|
return;
|
|
@@ -325,8 +328,8 @@ class DevtoolsCore {
|
|
|
325
328
|
this._wiringByPathKey.clear();
|
|
326
329
|
this._wiringEntryByBinding = new WeakMap();
|
|
327
330
|
this._observingSince = null;
|
|
328
|
-
this.
|
|
329
|
-
this.
|
|
331
|
+
this._watchFiredStats.clear();
|
|
332
|
+
this._tokenEmitStats.clear();
|
|
330
333
|
this._everAttachedKeys.clear();
|
|
331
334
|
this._notify("sources");
|
|
332
335
|
this._notify("roster");
|
|
@@ -367,13 +370,22 @@ class DevtoolsCore {
|
|
|
367
370
|
this._timeline = [];
|
|
368
371
|
this._notify("timeline");
|
|
369
372
|
}
|
|
370
|
-
/**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
+
/**
|
|
374
|
+
* 指定パスに束縛された配線(生存している binding のみ)。
|
|
375
|
+
* stateElement を渡すとそのツリーの配線だけに絞る(v2 は名前次元が無いため、
|
|
376
|
+
* 複数ツリーの同名パスは要素同一性で区別する)。stateElement を持たない
|
|
377
|
+
* 旧 payload 由来の entry は絞り込みでも残す(欠測を欠落にしない)。
|
|
378
|
+
*/
|
|
379
|
+
getWiringForPath(path, stateElement) {
|
|
380
|
+
const set = this._wiringByPathKey.get(pathKeyOf(path));
|
|
373
381
|
if (set === undefined) {
|
|
374
382
|
return [];
|
|
375
383
|
}
|
|
376
|
-
|
|
384
|
+
const alive = this._collectAlive(set);
|
|
385
|
+
if (stateElement === undefined) {
|
|
386
|
+
return alive;
|
|
387
|
+
}
|
|
388
|
+
return alive.filter((entry) => entry.stateElementRef === null || entry.stateElementRef.deref() === stateElement);
|
|
377
389
|
}
|
|
378
390
|
/** 全配線のスナップショット(生存している binding のみ) */
|
|
379
391
|
getAllWiring() {
|
|
@@ -421,10 +433,7 @@ class DevtoolsCore {
|
|
|
421
433
|
}
|
|
422
434
|
for (const root of roots) {
|
|
423
435
|
for (const declared of source.getDeclaredBindings(root)) {
|
|
424
|
-
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
const key = [declared.stateName, declared.statePathName, declared.propName,
|
|
436
|
+
const key = [declared.statePathName, declared.propName,
|
|
428
437
|
declared.bindingType, declared.origin, declared.raw].join(NUL);
|
|
429
438
|
if (seen.has(key)) {
|
|
430
439
|
continue;
|
|
@@ -440,16 +449,26 @@ class DevtoolsCore {
|
|
|
440
449
|
* 配線カバレッジ(§4): 宣言面(watchPaths / token 宣言 / canonical declared)と
|
|
441
450
|
* 実測面(watch-fired・token-emit の台帳・live 配線台帳)の突合。
|
|
442
451
|
* 「観測開始以降」の実測であることは observingSince を UI が常時表示して明示する。
|
|
452
|
+
*
|
|
453
|
+
* watch / token の実測は各行の属するツリー(roster entry の element)でスコープする
|
|
454
|
+
* (protocol v2 追補 — 複数ツリーの同名宣言を合算しない)。ツリー識別の無い
|
|
455
|
+
* 旧 payload 由来の実測はどの行にも合算で残す(欠測を欠落にしない)。
|
|
456
|
+
* stateElement を渡すとそのツリーの宣言行だけに絞る(getWiringForPath と同じ流儀)。
|
|
457
|
+
* binding 節(canonical declared × ever-attach)は宣言・実測ともツリー識別を
|
|
458
|
+
* 持たないため絞り込みの対象外。
|
|
443
459
|
*/
|
|
444
|
-
getCoverageReport() {
|
|
460
|
+
getCoverageReport(stateElement) {
|
|
445
461
|
const out = [];
|
|
446
462
|
for (const entry of this.getRoster()) {
|
|
447
463
|
const summary = entry.summary;
|
|
448
|
-
|
|
464
|
+
if (stateElement !== undefined && summary.element !== stateElement) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
// --- watch: 宣言(watchPaths)× 実測(watch-fired 台帳・自ツリー + 旧 payload 合算) ---
|
|
449
468
|
for (const path of summary.watchPaths ?? []) {
|
|
450
|
-
const count = this.
|
|
469
|
+
const count = this._measuredFor(this._watchFiredStats, pathKeyOf(path), summary.element)?.count ?? 0;
|
|
451
470
|
if (count > 0) {
|
|
452
|
-
out.push({
|
|
471
|
+
out.push({ kind: "watch", name: path, status: "fired", count, note: null });
|
|
453
472
|
continue;
|
|
454
473
|
}
|
|
455
474
|
// ワイルドカード行 watch に**リスト書き込み**が届く前提 = 各 `.*` 階層の
|
|
@@ -473,23 +492,23 @@ class DevtoolsCore {
|
|
|
473
492
|
}
|
|
474
493
|
if (missingList !== null) {
|
|
475
494
|
out.push({
|
|
476
|
-
|
|
495
|
+
kind: "watch", name: path, status: "prerequisite-missing", count: 0,
|
|
477
496
|
note: keyedKnown
|
|
478
497
|
? `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
498
|
: `no for binding observed for list "${missingList}" (a $listKeys declaration would still let list writes fire it)`,
|
|
480
499
|
});
|
|
481
500
|
continue;
|
|
482
501
|
}
|
|
483
|
-
out.push({
|
|
502
|
+
out.push({ kind: "watch", name: path, status: "never", count: 0, note: null });
|
|
484
503
|
}
|
|
485
504
|
// --- token: 宣言(commandTokenNames / eventTokenNames)× 実測(emit 台帳)。
|
|
486
505
|
// subscriberCount 0 = 空撃ち(§4): 全 emit が空撃ちなら emitted-unheard として
|
|
487
506
|
// 警告する(emit 側だけ配線されて受け手が一つも居ない構成ミスの検出)。 ---
|
|
488
507
|
for (const name of summary.commandTokenNames) {
|
|
489
|
-
out.push(tokenCoverageOf(
|
|
508
|
+
out.push(tokenCoverageOf("command", name, this._measuredFor(this._tokenEmitStats, tokenKeyOf("command", name), summary.element)));
|
|
490
509
|
}
|
|
491
510
|
for (const name of summary.eventTokenNames) {
|
|
492
|
-
out.push(tokenCoverageOf(
|
|
511
|
+
out.push(tokenCoverageOf("eventToken", name, this._measuredFor(this._tokenEmitStats, tokenKeyOf("event", name), summary.element)));
|
|
493
512
|
}
|
|
494
513
|
}
|
|
495
514
|
// --- binding: canonical declared(宣言)× live 配線台帳(実測)。canonical が
|
|
@@ -504,7 +523,7 @@ class DevtoolsCore {
|
|
|
504
523
|
continue;
|
|
505
524
|
if (decl.propName.startsWith("eventToken."))
|
|
506
525
|
continue;
|
|
507
|
-
const key = attachKeyOf(decl.
|
|
526
|
+
const key = attachKeyOf(decl.statePathName, decl.propName);
|
|
508
527
|
if (seen.has(key))
|
|
509
528
|
continue;
|
|
510
529
|
seen.add(key);
|
|
@@ -512,7 +531,7 @@ class DevtoolsCore {
|
|
|
512
531
|
// 破棄や GC(WeakRef pruning)で縮むため、瞬間値だと逆戻りする。
|
|
513
532
|
const attached = this._everAttachedKeys.has(key);
|
|
514
533
|
out.push({
|
|
515
|
-
|
|
534
|
+
kind: "binding",
|
|
516
535
|
name: `${decl.propName} ← ${decl.statePathName}`,
|
|
517
536
|
status: attached ? "attached" : "never-attached",
|
|
518
537
|
count: attached ? 1 : 0,
|
|
@@ -528,21 +547,33 @@ class DevtoolsCore {
|
|
|
528
547
|
if (source === undefined || typeof source.keys !== "function") {
|
|
529
548
|
return [];
|
|
530
549
|
}
|
|
531
|
-
return source.keys(entry.
|
|
550
|
+
return source.keys(entry.rootNode);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* roster entry のツリーに載るマウント記録(overlays — protocol v2・D20 の可視化)。
|
|
554
|
+
* overlays を実装しないランタイム(v2 より前)では null — UI はセクションごと
|
|
555
|
+
* 出さない(後方互換)。マウントが無いツリーは空配列。
|
|
556
|
+
*/
|
|
557
|
+
overlaysOf(entry) {
|
|
558
|
+
const source = this._sources.get(entry.sourceId);
|
|
559
|
+
if (source === undefined || typeof source.overlays !== "function") {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return source.overlays(entry.rootNode);
|
|
532
563
|
}
|
|
533
564
|
readValue(entry, path, indexes) {
|
|
534
565
|
const source = this._sources.get(entry.sourceId);
|
|
535
566
|
if (source === undefined) {
|
|
536
567
|
return undefined;
|
|
537
568
|
}
|
|
538
|
-
return source.read(entry.
|
|
569
|
+
return source.read(entry.rootNode, path, indexes);
|
|
539
570
|
}
|
|
540
571
|
writeValue(entry, path, value, indexes) {
|
|
541
572
|
const source = this._sources.get(entry.sourceId);
|
|
542
573
|
if (source === undefined) {
|
|
543
574
|
return;
|
|
544
575
|
}
|
|
545
|
-
source.write(entry.
|
|
576
|
+
source.write(entry.rootNode, path, value, indexes);
|
|
546
577
|
}
|
|
547
578
|
// --- internal ---
|
|
548
579
|
_notify(kind) {
|
|
@@ -552,19 +583,74 @@ class DevtoolsCore {
|
|
|
552
583
|
}
|
|
553
584
|
_refreshRosterOf(source) {
|
|
554
585
|
const entries = [];
|
|
586
|
+
const labelCounts = new Map();
|
|
555
587
|
for (const summary of source.getStateElements()) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
588
|
+
const base = rootLabelOf(summary.rootNode);
|
|
589
|
+
const seen = (labelCounts.get(base) ?? 0) + 1;
|
|
590
|
+
labelCounts.set(base, seen);
|
|
559
591
|
entries.push({
|
|
560
592
|
sourceId: source.id,
|
|
561
|
-
|
|
593
|
+
label: seen === 1 ? base : `${base}#${seen}`,
|
|
562
594
|
rootNode: summary.rootNode,
|
|
563
595
|
summary,
|
|
564
596
|
});
|
|
565
597
|
}
|
|
566
598
|
this._roster.set(source.id, entries);
|
|
567
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* key の実測台帳から stateElement 区分の stat を探すか作る(ingest 用)。
|
|
602
|
+
* payload の stateElement がオブジェクトでない(旧 payload / 直接生成 token)
|
|
603
|
+
* ときは null 区分 = 全ツリー合算面に積む。
|
|
604
|
+
*/
|
|
605
|
+
_statFor(map, key, stateElement) {
|
|
606
|
+
let entries = map.get(key);
|
|
607
|
+
if (entries === undefined) {
|
|
608
|
+
entries = [];
|
|
609
|
+
map.set(key, entries);
|
|
610
|
+
}
|
|
611
|
+
const element = typeof stateElement === "object" && stateElement !== null ? stateElement : null;
|
|
612
|
+
for (const entry of entries) {
|
|
613
|
+
if (element === null ? entry.stateElementRef === null : entry.stateElementRef?.deref() === element) {
|
|
614
|
+
return entry;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const created = {
|
|
618
|
+
stateElementRef: element === null ? null : new WeakRef(element),
|
|
619
|
+
count: 0,
|
|
620
|
+
zeroSubscriberCount: 0,
|
|
621
|
+
};
|
|
622
|
+
entries.push(created);
|
|
623
|
+
return created;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* key の実測を element のツリーへスコープして集計する(照会用)。
|
|
627
|
+
* null 区分(ツリー識別の無い旧 payload)は常に合算に含める。GC 済みツリーの
|
|
628
|
+
* 区分は遅延剪定(wiring 台帳の WeakRef pruning と同じ)。実測ゼロは undefined
|
|
629
|
+
* (= 宣言だけあって一度も観測していない)に畳む。
|
|
630
|
+
*/
|
|
631
|
+
_measuredFor(map, key, element) {
|
|
632
|
+
const entries = map.get(key);
|
|
633
|
+
if (entries === undefined) {
|
|
634
|
+
return undefined;
|
|
635
|
+
}
|
|
636
|
+
let count = 0;
|
|
637
|
+
let zeroSubscriberCount = 0;
|
|
638
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
639
|
+
const ref = entries[i].stateElementRef;
|
|
640
|
+
if (ref !== null && ref.deref() === undefined) {
|
|
641
|
+
entries.splice(i, 1);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (ref === null || ref.deref() === element) {
|
|
645
|
+
count += entries[i].count;
|
|
646
|
+
zeroSubscriberCount += entries[i].zeroSubscriberCount;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (entries.length === 0) {
|
|
650
|
+
map.delete(key);
|
|
651
|
+
}
|
|
652
|
+
return count === 0 ? undefined : { count, zeroSubscriberCount };
|
|
653
|
+
}
|
|
568
654
|
_collectAlive(set) {
|
|
569
655
|
const alive = [];
|
|
570
656
|
for (const entry of set) {
|
|
@@ -600,9 +686,6 @@ class DevtoolsCore {
|
|
|
600
686
|
_ingest(sourceId, event) {
|
|
601
687
|
switch (event.type) {
|
|
602
688
|
case "state:element-registered": {
|
|
603
|
-
if (this.isHiddenStateName(event.name)) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
689
|
const source = this._sources.get(sourceId);
|
|
607
690
|
if (source !== undefined) {
|
|
608
691
|
this._refreshRosterOf(source);
|
|
@@ -611,17 +694,13 @@ class DevtoolsCore {
|
|
|
611
694
|
this._appendTimeline({
|
|
612
695
|
sourceId,
|
|
613
696
|
kind: "element-registered",
|
|
614
|
-
|
|
615
|
-
label: event.name,
|
|
697
|
+
label: rootLabelOf(event.rootNode),
|
|
616
698
|
detail: "",
|
|
617
699
|
subscriberCount: null,
|
|
618
700
|
});
|
|
619
701
|
return;
|
|
620
702
|
}
|
|
621
703
|
case "state:element-unregistered": {
|
|
622
|
-
if (this.isHiddenStateName(event.name)) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
704
|
const source = this._sources.get(sourceId);
|
|
626
705
|
if (source !== undefined) {
|
|
627
706
|
this._refreshRosterOf(source);
|
|
@@ -630,25 +709,19 @@ class DevtoolsCore {
|
|
|
630
709
|
this._appendTimeline({
|
|
631
710
|
sourceId,
|
|
632
711
|
kind: "element-unregistered",
|
|
633
|
-
|
|
634
|
-
label: event.name,
|
|
712
|
+
label: rootLabelOf(event.rootNode),
|
|
635
713
|
detail: "",
|
|
636
714
|
subscriberCount: null,
|
|
637
715
|
});
|
|
638
716
|
return;
|
|
639
717
|
}
|
|
640
718
|
case "state:write": {
|
|
641
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
642
|
-
if (this.isHiddenStateName(stateName)) {
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
719
|
const detail = event.hasOldValue
|
|
646
720
|
? `${formatValue(event.value)} (was ${formatValue(event.oldValue)})`
|
|
647
721
|
: formatValue(event.value);
|
|
648
722
|
this._appendTimeline({
|
|
649
723
|
sourceId,
|
|
650
724
|
kind: "write",
|
|
651
|
-
stateName,
|
|
652
725
|
label: this._labelOf(event.absoluteAddress),
|
|
653
726
|
detail,
|
|
654
727
|
subscriberCount: null,
|
|
@@ -659,9 +732,6 @@ class DevtoolsCore {
|
|
|
659
732
|
const labels = [];
|
|
660
733
|
let total = 0;
|
|
661
734
|
for (const address of event.addresses) {
|
|
662
|
-
if (this.isHiddenStateName(address.absolutePathInfo.stateName)) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
735
|
total++;
|
|
666
736
|
if (labels.length < 3) {
|
|
667
737
|
labels.push(this._labelOf(address));
|
|
@@ -674,7 +744,6 @@ class DevtoolsCore {
|
|
|
674
744
|
this._appendTimeline({
|
|
675
745
|
sourceId,
|
|
676
746
|
kind: "batch",
|
|
677
|
-
stateName: null,
|
|
678
747
|
label: `${total} address${total === 1 ? "" : "es"}`,
|
|
679
748
|
detail: `${labels.join(", ")}${rest}`,
|
|
680
749
|
subscriberCount: null,
|
|
@@ -682,29 +751,28 @@ class DevtoolsCore {
|
|
|
682
751
|
return;
|
|
683
752
|
}
|
|
684
753
|
case "state:binding-added": {
|
|
685
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
686
|
-
if (this.isHiddenStateName(stateName)) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
754
|
const path = event.absoluteAddress.absolutePathInfo.pathInfo.path;
|
|
690
|
-
const key = pathKeyOf(
|
|
755
|
+
const key = pathKeyOf(path);
|
|
691
756
|
let set = this._wiringByPathKey.get(key);
|
|
692
757
|
if (set === undefined) {
|
|
693
758
|
set = new Set();
|
|
694
759
|
this._wiringByPathKey.set(key, set);
|
|
695
760
|
}
|
|
761
|
+
const stateElement = event.absoluteAddress.absolutePathInfo.stateElement;
|
|
696
762
|
const entry = {
|
|
697
763
|
sourceId,
|
|
698
|
-
stateName,
|
|
699
764
|
path,
|
|
700
765
|
propName: event.binding.propName,
|
|
701
766
|
bindingType: event.binding.bindingType,
|
|
702
767
|
bindingRef: new WeakRef(event.binding),
|
|
768
|
+
stateElementRef: typeof stateElement === "object" && stateElement !== null
|
|
769
|
+
? new WeakRef(stateElement)
|
|
770
|
+
: null,
|
|
703
771
|
};
|
|
704
772
|
set.add(entry);
|
|
705
773
|
this._wiringEntryByBinding.set(event.binding, entry);
|
|
706
774
|
// カバレッジの実測面(B2): attach の観測を ever 台帳へ積む(remove では消さない)
|
|
707
|
-
this._everAttachedKeys.add(attachKeyOf(
|
|
775
|
+
this._everAttachedKeys.add(attachKeyOf(path, event.binding.propName));
|
|
708
776
|
this._notify("wiring");
|
|
709
777
|
this._notify("coverage");
|
|
710
778
|
return;
|
|
@@ -715,7 +783,7 @@ class DevtoolsCore {
|
|
|
715
783
|
return;
|
|
716
784
|
}
|
|
717
785
|
this._wiringEntryByBinding.delete(event.binding);
|
|
718
|
-
const key = pathKeyOf(entry.
|
|
786
|
+
const key = pathKeyOf(entry.path);
|
|
719
787
|
const set = this._wiringByPathKey.get(key);
|
|
720
788
|
if (set !== undefined) {
|
|
721
789
|
set.delete(entry);
|
|
@@ -727,41 +795,26 @@ class DevtoolsCore {
|
|
|
727
795
|
return;
|
|
728
796
|
}
|
|
729
797
|
case "state:binding-cleared": {
|
|
730
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
731
798
|
const path = event.absoluteAddress.absolutePathInfo.pathInfo.path;
|
|
732
|
-
const key = pathKeyOf(
|
|
799
|
+
const key = pathKeyOf(path);
|
|
733
800
|
if (this._wiringByPathKey.delete(key)) {
|
|
734
801
|
this._notify("wiring");
|
|
735
802
|
}
|
|
736
803
|
return;
|
|
737
804
|
}
|
|
738
805
|
case "state:token-emit": {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (event.
|
|
745
|
-
|
|
746
|
-
const stat = this._tokenEmitCounts.get(emitKey);
|
|
747
|
-
if (stat === undefined) {
|
|
748
|
-
this._tokenEmitCounts.set(emitKey, {
|
|
749
|
-
count: 1,
|
|
750
|
-
zeroSubscriberCount: event.subscriberCount === 0 ? 1 : 0,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
else {
|
|
754
|
-
stat.count += 1;
|
|
755
|
-
if (event.subscriberCount === 0) {
|
|
756
|
-
stat.zeroSubscriberCount += 1;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
this._notify("coverage");
|
|
806
|
+
// カバレッジの実測面: emit 回数と空撃ち回数を、発火元ツリー
|
|
807
|
+
// (payload の stateElement — protocol v2 追補)別の区分に積む(§4)。
|
|
808
|
+
// 識別の無い旧 payload は null 区分 = 全ツリー合算面へ。
|
|
809
|
+
const stat = this._statFor(this._tokenEmitStats, tokenKeyOf(event.kind, event.tokenName), event.stateElement);
|
|
810
|
+
stat.count += 1;
|
|
811
|
+
if (event.subscriberCount === 0) {
|
|
812
|
+
stat.zeroSubscriberCount += 1;
|
|
760
813
|
}
|
|
814
|
+
this._notify("coverage");
|
|
761
815
|
this._appendTimeline({
|
|
762
816
|
sourceId,
|
|
763
817
|
kind: event.kind,
|
|
764
|
-
stateName: event.stateName,
|
|
765
818
|
label: event.tokenName,
|
|
766
819
|
detail: formatArgs(event.args),
|
|
767
820
|
subscriberCount: event.subscriberCount,
|
|
@@ -769,26 +822,19 @@ class DevtoolsCore {
|
|
|
769
822
|
return;
|
|
770
823
|
}
|
|
771
824
|
case "state:watch-fired": {
|
|
772
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
825
|
// timeline 行にはしない(発火は高頻度になりうる活動でありカバレッジが受け皿)。
|
|
776
|
-
|
|
777
|
-
this.
|
|
826
|
+
// 発火元ツリー(payload の stateElement — protocol v2 追補)別に数える。
|
|
827
|
+
this._statFor(this._watchFiredStats, pathKeyOf(event.path), event.stateElement).count += 1;
|
|
778
828
|
this._notify("coverage");
|
|
779
829
|
return;
|
|
780
830
|
}
|
|
781
831
|
case "state:watch-error": {
|
|
782
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
832
|
// ランタイム側は例外を握って drain を守るため、ここに出ないと失敗が
|
|
786
833
|
// どこにも現れない(console を見ていない限り)。phase を detail の先頭に
|
|
787
834
|
// 置くのは、getter の評価失敗とハンドラの失敗で直し方が違うため。
|
|
788
835
|
this._appendTimeline({
|
|
789
836
|
sourceId,
|
|
790
837
|
kind: "watch-error",
|
|
791
|
-
stateName: event.stateName,
|
|
792
838
|
label: event.path,
|
|
793
839
|
detail: `${event.phase}: ${formatError(event.error)}`,
|
|
794
840
|
subscriberCount: null,
|
|
@@ -799,9 +845,7 @@ class DevtoolsCore {
|
|
|
799
845
|
this._appendTimeline({
|
|
800
846
|
sourceId,
|
|
801
847
|
kind: "watch-chain-limit",
|
|
802
|
-
|
|
803
|
-
// 打ち切りはバッチ単位で state 名を持たない(複数 state のアドレスが
|
|
804
|
-
// 載りうる)ため、hidden 判定はここでは行わない。
|
|
848
|
+
// 打ち切りはバッチ単位(複数ツリーのアドレスが載りうる)— 行の主語は深さ上限
|
|
805
849
|
label: `depth > ${event.maxDepth}`,
|
|
806
850
|
detail: event.paths.join(", "),
|
|
807
851
|
subscriberCount: null,
|
|
@@ -809,15 +853,11 @@ class DevtoolsCore {
|
|
|
809
853
|
return;
|
|
810
854
|
}
|
|
811
855
|
case "state:path-unresolved": {
|
|
812
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
856
|
// ランタイムは warn で続行する(既存ページを止めないため)。lint を
|
|
816
857
|
// 走らせていない相手には、ここが「配線が死んでいる」唯一の可視面になる。
|
|
817
858
|
this._appendTimeline({
|
|
818
859
|
sourceId,
|
|
819
860
|
kind: "path-unresolved",
|
|
820
|
-
stateName: event.stateName,
|
|
821
861
|
label: event.path,
|
|
822
862
|
detail: `${event.source}: "${event.missingSegment}" is not declared`,
|
|
823
863
|
subscriberCount: null,
|
|
@@ -825,15 +865,11 @@ class DevtoolsCore {
|
|
|
825
865
|
return;
|
|
826
866
|
}
|
|
827
867
|
case "state:binding-apply-error": {
|
|
828
|
-
if (this.isHiddenStateName(event.stateName)) {
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
868
|
// 隔離された適用失敗。bindingType を detail 先頭に置くのは、構造
|
|
832
869
|
// (for / if)の失敗と値の失敗で影響範囲が違うため。
|
|
833
870
|
this._appendTimeline({
|
|
834
871
|
sourceId,
|
|
835
872
|
kind: "binding-apply-error",
|
|
836
|
-
stateName: event.stateName,
|
|
837
873
|
label: event.path,
|
|
838
874
|
detail: `${event.bindingType}: ${formatError(event.error)}`,
|
|
839
875
|
subscriberCount: null,
|
|
@@ -841,12 +877,10 @@ class DevtoolsCore {
|
|
|
841
877
|
return;
|
|
842
878
|
}
|
|
843
879
|
case "propagation:suppressed": {
|
|
844
|
-
// two-way
|
|
845
|
-
// ため hidden 判定はここでは行わない。
|
|
880
|
+
// two-way エコーの辺単位抑止(主語は node + member — パスではない)
|
|
846
881
|
this._appendTimeline({
|
|
847
882
|
sourceId,
|
|
848
883
|
kind: "propagation-suppressed",
|
|
849
|
-
stateName: null,
|
|
850
884
|
label: event.member,
|
|
851
885
|
detail: `${event.reason} (tx ${event.transactionId}, edge ${event.edgeId})`,
|
|
852
886
|
subscriberCount: null,
|
|
@@ -854,14 +888,9 @@ class DevtoolsCore {
|
|
|
854
888
|
return;
|
|
855
889
|
}
|
|
856
890
|
case "propagation:coalesced": {
|
|
857
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
858
|
-
if (this.isHiddenStateName(stateName)) {
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
891
|
this._appendTimeline({
|
|
862
892
|
sourceId,
|
|
863
893
|
kind: "propagation-coalesced",
|
|
864
|
-
stateName,
|
|
865
894
|
label: this._labelOf(event.absoluteAddress),
|
|
866
895
|
detail: `tx ${event.droppedTransactionId} dropped (winner tx ${event.winnerTransactionId})`,
|
|
867
896
|
subscriberCount: null,
|
|
@@ -869,14 +898,9 @@ class DevtoolsCore {
|
|
|
869
898
|
return;
|
|
870
899
|
}
|
|
871
900
|
case "propagation:hop-limit": {
|
|
872
|
-
const stateName = event.absoluteAddress.absolutePathInfo.stateName;
|
|
873
|
-
if (this.isHiddenStateName(stateName)) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
901
|
this._appendTimeline({
|
|
877
902
|
sourceId,
|
|
878
903
|
kind: "propagation-hop-limit",
|
|
879
|
-
stateName,
|
|
880
904
|
label: this._labelOf(event.absoluteAddress),
|
|
881
905
|
detail: `hop ${event.hop} (tx ${event.transactionId})`,
|
|
882
906
|
subscriberCount: null,
|
|
@@ -894,7 +918,6 @@ class DevtoolsCore {
|
|
|
894
918
|
this._appendTimeline({
|
|
895
919
|
sourceId,
|
|
896
920
|
kind: "contract-drift",
|
|
897
|
-
stateName: null,
|
|
898
921
|
label: event.tag,
|
|
899
922
|
detail: `${event.reason}${memberPart}${eventPart}`,
|
|
900
923
|
subscriberCount: null,
|
|
@@ -922,7 +945,7 @@ class DevtoolsCore {
|
|
|
922
945
|
* 「宣言レベルの配線ビュー」を組む。ライブ台帳と違い binding 実体・
|
|
923
946
|
* 接続状態は分からない(UI では "declared" バッジで区別する)。
|
|
924
947
|
*
|
|
925
|
-
* パースは表示目的の簡易版(`prop[#mod]: path[
|
|
948
|
+
* パースは表示目的の簡易版(`prop[#mod]: path[|filters]` を
|
|
926
949
|
* `;` 区切りで分解するだけ)。正確なセマンティクスの正本は
|
|
927
950
|
* @wcstack/state の bindTextParser であり、ここでは追随しない。
|
|
928
951
|
*/
|
|
@@ -943,18 +966,13 @@ function parseEntry(element, raw, origin) {
|
|
|
943
966
|
return null;
|
|
944
967
|
}
|
|
945
968
|
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
|
-
}
|
|
969
|
+
// v2: `@name` セレクタは撤去済み(ランタイムは parse error にする)。
|
|
970
|
+
// 簡易パーサはパスをそのまま流す — 壊れた宣言の可視化はランタイム側の役目。
|
|
971
|
+
const path = pathPart;
|
|
953
972
|
return {
|
|
954
973
|
element,
|
|
955
974
|
propName,
|
|
956
975
|
path,
|
|
957
|
-
stateName,
|
|
958
976
|
filters: filterParts.filter((part) => part.length > 0),
|
|
959
977
|
origin,
|
|
960
978
|
raw: trimmed,
|
|
@@ -1108,6 +1126,11 @@ header .spacer { flex: 1; }
|
|
|
1108
1126
|
.notice { color: #efe3a0; padding: 2px 0 6px; }
|
|
1109
1127
|
.notice button { font: inherit; margin-left: 6px; cursor: pointer; }
|
|
1110
1128
|
.wiring-row .detail { color: #6f88a3; }
|
|
1129
|
+
.overlays-heading { margin-top: 10px; }
|
|
1130
|
+
.overlay-row { padding: 1px 0; white-space: nowrap; }
|
|
1131
|
+
.overlay-row .prop { color: #b7f0c0; }
|
|
1132
|
+
.overlay-row .path { color: #9fd0ff; }
|
|
1133
|
+
.overlay-row .detail { color: #6f88a3; }
|
|
1111
1134
|
.wiring-tabs { padding: 0 0 4px; }
|
|
1112
1135
|
.wiring-tabs button { font: inherit; cursor: pointer; margin-right: 4px; opacity: 0.6; }
|
|
1113
1136
|
.wiring-tabs button.active { opacity: 1; font-weight: bold; }
|
|
@@ -1178,13 +1201,8 @@ class WcsDevtools extends HTMLElement {
|
|
|
1178
1201
|
this._buildShadow();
|
|
1179
1202
|
}
|
|
1180
1203
|
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
1204
|
this._core = new DevtoolsCore({
|
|
1186
1205
|
timelineCapacity: Number.isFinite(capacity) && capacity > 0 ? capacity : undefined,
|
|
1187
|
-
hiddenStateNames: hidden,
|
|
1188
1206
|
});
|
|
1189
1207
|
this._removeCoreListener = this._core.onChange((kind) => {
|
|
1190
1208
|
if (kind === "roster" || kind === "sources") {
|
|
@@ -1461,7 +1479,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1461
1479
|
}
|
|
1462
1480
|
}
|
|
1463
1481
|
_rosterKey(entry) {
|
|
1464
|
-
return `${entry.sourceId}:${entry.
|
|
1482
|
+
return `${entry.sourceId}:${entry.label}`;
|
|
1465
1483
|
}
|
|
1466
1484
|
_selectedRoster() {
|
|
1467
1485
|
const core = this._core;
|
|
@@ -1481,7 +1499,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1481
1499
|
select.replaceChildren(...roster.map((entry) => {
|
|
1482
1500
|
const option = document.createElement("option");
|
|
1483
1501
|
option.value = this._rosterKey(entry);
|
|
1484
|
-
option.textContent = `${entry.
|
|
1502
|
+
option.textContent = `${entry.label} (${entry.sourceId.slice(0, 12)})`;
|
|
1485
1503
|
option.selected = selected !== null && this._rosterKey(entry) === this._rosterKey(selected);
|
|
1486
1504
|
return option;
|
|
1487
1505
|
}));
|
|
@@ -1494,11 +1512,73 @@ class WcsDevtools extends HTMLElement {
|
|
|
1494
1512
|
const keys = core.keysOf(selected);
|
|
1495
1513
|
if (keys.length === 0) {
|
|
1496
1514
|
body.append(this._emptyRow("no readable keys (runtime without keys() API?)"));
|
|
1515
|
+
// keys が読めないランタイムでも overlays は独立に読める可能性があるため出す
|
|
1516
|
+
this._renderOverlaysSection(body, selected);
|
|
1497
1517
|
return;
|
|
1498
1518
|
}
|
|
1499
1519
|
for (const key of keys) {
|
|
1500
1520
|
this._renderTreeNode(body, selected, { path: key, indexes: [] }, key, 0);
|
|
1501
1521
|
}
|
|
1522
|
+
this._renderOverlaysSection(body, selected);
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* 選択ツリーのマウント記録セクション(overlays — protocol v2・D20 の可視化)。
|
|
1526
|
+
* マウントの私有キー・getter はオーバーレイ専用アドレス空間(マーカー `#m<id>`)に
|
|
1527
|
+
* 住み、上の状態ツリー(keys/read)には現れないため、ここが唯一の可視面。
|
|
1528
|
+
* overlays 未提供の旧ランタイム(null)ではセクションごと出さない(後方互換)。
|
|
1529
|
+
* マウント無し(空配列)も見出しだけ残さない。pull は State ペインの再描画
|
|
1530
|
+
* (roster / sources 変更・ツリー切替)にそのまま乗る — 専用 polling は足さない
|
|
1531
|
+
* (観測者効果の排除、devtools-tag-design.md の rAF 合流と同じ姿勢)。
|
|
1532
|
+
*/
|
|
1533
|
+
_renderOverlaysSection(body, entry) {
|
|
1534
|
+
const overlays = this._core.overlaysOf(entry);
|
|
1535
|
+
if (overlays === null || overlays.length === 0) {
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
const heading = document.createElement("h3");
|
|
1539
|
+
heading.className = "overlays-heading";
|
|
1540
|
+
heading.textContent = `Overlays (${overlays.length} mount${overlays.length === 1 ? "" : "s"})`;
|
|
1541
|
+
body.append(heading);
|
|
1542
|
+
for (const overlay of overlays) {
|
|
1543
|
+
body.append(this._overlayRow(overlay));
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
/** マウント記録 1 件の描画(マーカー・コンポーネント・マウント表・私有面)。 */
|
|
1547
|
+
_overlayRow(overlay) {
|
|
1548
|
+
const row = document.createElement("div");
|
|
1549
|
+
row.className = "overlay-row";
|
|
1550
|
+
const marker = document.createElement("span");
|
|
1551
|
+
marker.className = "badge-tag";
|
|
1552
|
+
marker.textContent = overlay.marker;
|
|
1553
|
+
const tag = document.createElement("span");
|
|
1554
|
+
tag.className = "prop";
|
|
1555
|
+
tag.textContent = `<${overlay.componentTag}>`;
|
|
1556
|
+
const prop = document.createElement("span");
|
|
1557
|
+
prop.className = "path";
|
|
1558
|
+
// stateProp はホスト側の `state[.sub]:` 宣言の書き手語彙
|
|
1559
|
+
prop.textContent = overlay.stateProp;
|
|
1560
|
+
row.append(marker, document.createTextNode(" "), tag, document.createTextNode(" "), prop);
|
|
1561
|
+
const details = [];
|
|
1562
|
+
for (const entry of overlay.mountTable) {
|
|
1563
|
+
// 内側接頭辞が空 = ルートエントリ。外側パスへの対応で読ませる
|
|
1564
|
+
details.push(`${entry.inner === "" ? "(root)" : entry.inner} → ${entry.outer}`);
|
|
1565
|
+
}
|
|
1566
|
+
if (overlay.delta > 0) {
|
|
1567
|
+
details.push(`Δ${overlay.delta}`);
|
|
1568
|
+
}
|
|
1569
|
+
if (overlay.privateKeys.length > 0) {
|
|
1570
|
+
details.push(`private: ${overlay.privateKeys.join(", ")}`);
|
|
1571
|
+
}
|
|
1572
|
+
if (overlay.getterKeys.length > 0) {
|
|
1573
|
+
details.push(`getters: ${overlay.getterKeys.join(", ")}`);
|
|
1574
|
+
}
|
|
1575
|
+
if (details.length > 0) {
|
|
1576
|
+
const detail = document.createElement("span");
|
|
1577
|
+
detail.className = "detail";
|
|
1578
|
+
detail.textContent = ` ${details.join(" · ")}`;
|
|
1579
|
+
row.append(detail);
|
|
1580
|
+
}
|
|
1581
|
+
return row;
|
|
1502
1582
|
}
|
|
1503
1583
|
_renderTreeNode(container, entry, ref, label, depth) {
|
|
1504
1584
|
const core = this._core;
|
|
@@ -1632,8 +1712,9 @@ class WcsDevtools extends HTMLElement {
|
|
|
1632
1712
|
}
|
|
1633
1713
|
else if (this._selectedPath !== null) {
|
|
1634
1714
|
const selected = this._selectedRoster();
|
|
1715
|
+
// 選択中のツリーでスコープ(複数ツリーの同名パスを混ぜない)
|
|
1635
1716
|
entries =
|
|
1636
|
-
selected !== null ? core.getWiringForPath(
|
|
1717
|
+
selected !== null ? core.getWiringForPath(this._selectedPath, selected.summary.element) : [];
|
|
1637
1718
|
contextLabel = this._selectedPath;
|
|
1638
1719
|
}
|
|
1639
1720
|
else {
|
|
@@ -1701,7 +1782,10 @@ class WcsDevtools extends HTMLElement {
|
|
|
1701
1782
|
? "not observing"
|
|
1702
1783
|
: `observing since ${sinceLabel} (declared vs measured after this point)`;
|
|
1703
1784
|
body.append(info);
|
|
1704
|
-
|
|
1785
|
+
// 選択中のツリーでスコープ(複数ツリーの同名 watch パス / token 名を混ぜない —
|
|
1786
|
+
// wiring のパス文脈と同じ流儀。protocol v2 追補のツリー識別が実測面を分ける)
|
|
1787
|
+
const selected = this._selectedRoster();
|
|
1788
|
+
const report = core.getCoverageReport(selected?.summary.element);
|
|
1705
1789
|
if (report.length === 0) {
|
|
1706
1790
|
body.append(this._emptyRow("nothing declared to cover"));
|
|
1707
1791
|
return;
|
|
@@ -1726,7 +1810,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1726
1810
|
}
|
|
1727
1811
|
const label = document.createElement("span");
|
|
1728
1812
|
label.className = "path";
|
|
1729
|
-
label.textContent = ` ${entry.name}
|
|
1813
|
+
label.textContent = ` ${entry.name} `;
|
|
1730
1814
|
row.append(kind, document.createTextNode(" "), label, status);
|
|
1731
1815
|
if (entry.note !== null) {
|
|
1732
1816
|
// note はホバー頼み(title)にせず常時表示する — 前提未成立の理由は
|
|
@@ -1752,7 +1836,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1752
1836
|
const path = document.createElement("span");
|
|
1753
1837
|
path.className = "path";
|
|
1754
1838
|
const filters = entry.outFilters.map((f) => ` | ${f.filterName}(${f.args.join(",")})`).join("");
|
|
1755
|
-
path.textContent = `${entry.statePathName}
|
|
1839
|
+
path.textContent = `${entry.statePathName}${filters}`;
|
|
1756
1840
|
row.append(type, document.createTextNode(" "), prop, document.createTextNode(" ← "), path);
|
|
1757
1841
|
if (entry.node !== null) {
|
|
1758
1842
|
const target = entry.node;
|
|
@@ -1781,7 +1865,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1781
1865
|
const arrow = document.createTextNode(" ← ");
|
|
1782
1866
|
const path = document.createElement("span");
|
|
1783
1867
|
path.className = "path";
|
|
1784
|
-
path.textContent =
|
|
1868
|
+
path.textContent = entry.path;
|
|
1785
1869
|
const type = document.createElement("span");
|
|
1786
1870
|
type.className = "badge-tag";
|
|
1787
1871
|
type.textContent = entry.bindingType;
|
|
@@ -1805,7 +1889,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1805
1889
|
prop.textContent = entry.propName;
|
|
1806
1890
|
const path = document.createElement("span");
|
|
1807
1891
|
path.className = "path";
|
|
1808
|
-
path.textContent =
|
|
1892
|
+
path.textContent = entry.path;
|
|
1809
1893
|
row.append(type, document.createTextNode(" "), prop, document.createTextNode(" ← "), path);
|
|
1810
1894
|
row.addEventListener("click", () => {
|
|
1811
1895
|
this._highlightNodes([entry.element]);
|
|
@@ -1870,8 +1954,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1870
1954
|
}
|
|
1871
1955
|
const label = document.createElement("span");
|
|
1872
1956
|
label.className = "label";
|
|
1873
|
-
|
|
1874
|
-
label.textContent = ` ${entry.label}${stateName} `;
|
|
1957
|
+
label.textContent = ` ${entry.label} `;
|
|
1875
1958
|
const detail = document.createElement("span");
|
|
1876
1959
|
detail.className = "detail";
|
|
1877
1960
|
detail.textContent = entry.detail;
|
|
@@ -1888,7 +1971,7 @@ class WcsDevtools extends HTMLElement {
|
|
|
1888
1971
|
_highlightPath(entry, path) {
|
|
1889
1972
|
const core = this._core;
|
|
1890
1973
|
const nodes = [];
|
|
1891
|
-
for (const wiring of core.getWiringForPath(entry.
|
|
1974
|
+
for (const wiring of core.getWiringForPath(path, entry.summary.element)) {
|
|
1892
1975
|
const binding = wiring.bindingRef.deref();
|
|
1893
1976
|
if (binding !== undefined) {
|
|
1894
1977
|
nodes.push(binding.node, binding.replaceNode);
|
|
@@ -1954,5 +2037,5 @@ function bootstrapDevtools() {
|
|
|
1954
2037
|
}
|
|
1955
2038
|
}
|
|
1956
2039
|
|
|
1957
|
-
export { DEVTOOLS_HOOK_GLOBAL, DEVTOOLS_PROTOCOL_VERSION, DevtoolsCore,
|
|
2040
|
+
export { DEVTOOLS_HOOK_GLOBAL, DEVTOOLS_PROTOCOL_VERSION, DevtoolsCore, WcsDevtools, bootstrapDevtools, formatArgs, formatValue, getOrCreateHookRegistry, scanDeclaredBindings };
|
|
1958
2041
|
//# sourceMappingURL=index.esm.js.map
|