@wcstack/devtools 2.0.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/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +89 -8
- package/dist/index.esm.js +171 -31
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,29 @@ interface IStateElementSummaryLike {
|
|
|
72
72
|
*/
|
|
73
73
|
readonly keyedListPaths?: ReadonlySet<string> | null;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* マウント記録 1 件の要約(overlays の要素 — protocol v2・D20 の可視化)。
|
|
77
|
+
* マウントの私有キー・getter はオーバーレイ専用アドレス空間(マーカー `#m<id>`)に
|
|
78
|
+
* 住み、状態ツリーの keys/read には現れないため、これが唯一の可視面になる。
|
|
79
|
+
*/
|
|
80
|
+
interface IMountOverlaySummaryLike {
|
|
81
|
+
/** D20 の予約セグメント(`#m<id>`) */
|
|
82
|
+
readonly marker: string;
|
|
83
|
+
/** マウントされたコンポーネントのタグ名(小文字) */
|
|
84
|
+
readonly componentTag: string;
|
|
85
|
+
readonly stateProp: string;
|
|
86
|
+
/** マウント表: 内側接頭辞(空 = ルートエントリ)→ 外側パス */
|
|
87
|
+
readonly mountTable: readonly {
|
|
88
|
+
readonly inner: string;
|
|
89
|
+
readonly outer: string;
|
|
90
|
+
}[];
|
|
91
|
+
/** `$n` 補正の Δ(ルート接頭辞のワイルドカード数) */
|
|
92
|
+
readonly delta: number;
|
|
93
|
+
/** 私有キー(オーバーレイ空間に住む own data key) */
|
|
94
|
+
readonly privateKeys: readonly string[];
|
|
95
|
+
/** マーカーパスに載る getter のキー */
|
|
96
|
+
readonly getterKeys: readonly string[];
|
|
97
|
+
}
|
|
75
98
|
/**
|
|
76
99
|
* 宣言レベルのバインディング 1 件(getDeclaredBindings の要素・protocol v1 追補)。
|
|
77
100
|
* ランタイム正本パーサの結果が構造的に流れる。宣言タプルで dedupe 済みの
|
|
@@ -128,6 +151,12 @@ type DevtoolsEventLike = {
|
|
|
128
151
|
readonly tokenName: string;
|
|
129
152
|
readonly args: readonly unknown[];
|
|
130
153
|
readonly subscriberCount: number;
|
|
154
|
+
/**
|
|
155
|
+
* 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。
|
|
156
|
+
* 旧ランタイム・registry 非経由の token の payload には無い(optional)—
|
|
157
|
+
* 無い emit の実測はツリー別に分けられないため、全ツリーの照会へ合算で残す。
|
|
158
|
+
*/
|
|
159
|
+
readonly stateElement?: unknown;
|
|
131
160
|
} | {
|
|
132
161
|
readonly type: "state:watch-error";
|
|
133
162
|
readonly phase: "prime" | "evaluate" | "handler";
|
|
@@ -140,6 +169,13 @@ type DevtoolsEventLike = {
|
|
|
140
169
|
} | {
|
|
141
170
|
readonly type: "state:watch-fired";
|
|
142
171
|
readonly path: string;
|
|
172
|
+
/**
|
|
173
|
+
* 発火元ツリーの state 要素(protocol v2 追補 2026-09-05・additive)。
|
|
174
|
+
* 複数ツリーが同名 watch パスを宣言するページで実測台帳をツリー別に分ける。
|
|
175
|
+
* 旧ランタイムの payload には無い(optional)— 無い発火は全ツリーの照会へ
|
|
176
|
+
* 合算で残す。
|
|
177
|
+
*/
|
|
178
|
+
readonly stateElement?: unknown;
|
|
143
179
|
} | {
|
|
144
180
|
readonly type: "state:path-unresolved";
|
|
145
181
|
readonly source: "binding" | "watch";
|
|
@@ -190,6 +226,12 @@ interface IDevtoolsSourceLike {
|
|
|
190
226
|
getStateElements(): IStateElementSummaryLike[];
|
|
191
227
|
/** protocol v1 追補 API。古いランタイムには無い可能性があるため optional 扱いで呼ぶ */
|
|
192
228
|
keys?(rootNode: Node): string[];
|
|
229
|
+
/**
|
|
230
|
+
* protocol v2 API(optional 扱いで呼ぶ)。rootNode のツリーに載っている
|
|
231
|
+
* マウント記録の列挙(D20 の可視化)。マウントが無ければ空配列。
|
|
232
|
+
* v2 より前のランタイムには無いため、無ければ UI はセクションごと出さない。
|
|
233
|
+
*/
|
|
234
|
+
overlays?(rootNode: Node): IMountOverlaySummaryLike[];
|
|
193
235
|
read(rootNode: Node, path: string, indexes?: number[]): unknown;
|
|
194
236
|
write(rootNode: Node, path: string, value: unknown, indexes?: number[]): void;
|
|
195
237
|
/**
|
|
@@ -295,13 +337,14 @@ declare class DevtoolsCore {
|
|
|
295
337
|
private _changeListeners;
|
|
296
338
|
/** 観測開始時刻(connect 時の performance.now。未接続は null)。 */
|
|
297
339
|
private _observingSince;
|
|
298
|
-
/** watch
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
340
|
+
/** watch 発火実測(path → ツリー別区分列)。カバレッジの実測面。
|
|
341
|
+
* protocol v2 追補で payload がツリー識別(stateElement)を持つようになり、
|
|
342
|
+
* 複数ツリーが同名 watch パスを宣言するページでもツリー別に数えられる。
|
|
343
|
+
* 識別の無い旧 payload は null 区分に積み、どのツリーの照会にも合算で残す
|
|
344
|
+
* (slice 29 non-blocking 記録の解消)。 */
|
|
345
|
+
private _watchFiredStats;
|
|
346
|
+
/** token emit 実測(kind + NUL + name → ツリー別区分列)。上の追補注記と同じ。 */
|
|
347
|
+
private _tokenEmitStats;
|
|
305
348
|
/** 観測開始以降に一度でも attach を観測した宣言キー(attachKeyOf)。
|
|
306
349
|
* live 配線台帳は WeakRef pruning / binding-removed で縮むため、binding
|
|
307
350
|
* カバレッジの attached 判定はこちらで行う(瞬間値で判定すると行の
|
|
@@ -345,14 +388,40 @@ declare class DevtoolsCore {
|
|
|
345
388
|
* 配線カバレッジ(§4): 宣言面(watchPaths / token 宣言 / canonical declared)と
|
|
346
389
|
* 実測面(watch-fired・token-emit の台帳・live 配線台帳)の突合。
|
|
347
390
|
* 「観測開始以降」の実測であることは observingSince を UI が常時表示して明示する。
|
|
391
|
+
*
|
|
392
|
+
* watch / token の実測は各行の属するツリー(roster entry の element)でスコープする
|
|
393
|
+
* (protocol v2 追補 — 複数ツリーの同名宣言を合算しない)。ツリー識別の無い
|
|
394
|
+
* 旧 payload 由来の実測はどの行にも合算で残す(欠測を欠落にしない)。
|
|
395
|
+
* stateElement を渡すとそのツリーの宣言行だけに絞る(getWiringForPath と同じ流儀)。
|
|
396
|
+
* binding 節(canonical declared × ever-attach)は宣言・実測ともツリー識別を
|
|
397
|
+
* 持たないため絞り込みの対象外。
|
|
348
398
|
*/
|
|
349
|
-
getCoverageReport(): ICoverageEntry[];
|
|
399
|
+
getCoverageReport(stateElement?: unknown): ICoverageEntry[];
|
|
350
400
|
/** roster entry の state からトップレベルキーを列挙(keys 未実装ランタイムは空) */
|
|
351
401
|
keysOf(entry: IRosterEntry): string[];
|
|
402
|
+
/**
|
|
403
|
+
* roster entry のツリーに載るマウント記録(overlays — protocol v2・D20 の可視化)。
|
|
404
|
+
* overlays を実装しないランタイム(v2 より前)では null — UI はセクションごと
|
|
405
|
+
* 出さない(後方互換)。マウントが無いツリーは空配列。
|
|
406
|
+
*/
|
|
407
|
+
overlaysOf(entry: IRosterEntry): IMountOverlaySummaryLike[] | null;
|
|
352
408
|
readValue(entry: IRosterEntry, path: string, indexes?: number[]): unknown;
|
|
353
409
|
writeValue(entry: IRosterEntry, path: string, value: unknown, indexes?: number[]): void;
|
|
354
410
|
private _notify;
|
|
355
411
|
private _refreshRosterOf;
|
|
412
|
+
/**
|
|
413
|
+
* key の実測台帳から stateElement 区分の stat を探すか作る(ingest 用)。
|
|
414
|
+
* payload の stateElement がオブジェクトでない(旧 payload / 直接生成 token)
|
|
415
|
+
* ときは null 区分 = 全ツリー合算面に積む。
|
|
416
|
+
*/
|
|
417
|
+
private _statFor;
|
|
418
|
+
/**
|
|
419
|
+
* key の実測を element のツリーへスコープして集計する(照会用)。
|
|
420
|
+
* null 区分(ツリー識別の無い旧 payload)は常に合算に含める。GC 済みツリーの
|
|
421
|
+
* 区分は遅延剪定(wiring 台帳の WeakRef pruning と同じ)。実測ゼロは undefined
|
|
422
|
+
* (= 宣言だけあって一度も観測していない)に畳む。
|
|
423
|
+
*/
|
|
424
|
+
private _measuredFor;
|
|
356
425
|
private _collectAlive;
|
|
357
426
|
private _appendTimeline;
|
|
358
427
|
private _labelOf;
|
|
@@ -415,6 +484,18 @@ declare class WcsDevtools extends HTMLElement {
|
|
|
415
484
|
private _rosterKey;
|
|
416
485
|
private _selectedRoster;
|
|
417
486
|
private _renderStatePane;
|
|
487
|
+
/**
|
|
488
|
+
* 選択ツリーのマウント記録セクション(overlays — protocol v2・D20 の可視化)。
|
|
489
|
+
* マウントの私有キー・getter はオーバーレイ専用アドレス空間(マーカー `#m<id>`)に
|
|
490
|
+
* 住み、上の状態ツリー(keys/read)には現れないため、ここが唯一の可視面。
|
|
491
|
+
* overlays 未提供の旧ランタイム(null)ではセクションごと出さない(後方互換)。
|
|
492
|
+
* マウント無し(空配列)も見出しだけ残さない。pull は State ペインの再描画
|
|
493
|
+
* (roster / sources 変更・ツリー切替)にそのまま乗る — 専用 polling は足さない
|
|
494
|
+
* (観測者効果の排除、devtools-tag-design.md の rAF 合流と同じ姿勢)。
|
|
495
|
+
*/
|
|
496
|
+
private _renderOverlaysSection;
|
|
497
|
+
/** マウント記録 1 件の描画(マーカー・コンポーネント・マウント表・私有面)。 */
|
|
498
|
+
private _overlayRow;
|
|
418
499
|
private _renderTreeNode;
|
|
419
500
|
private _beginEdit;
|
|
420
501
|
private _renderWiringPane;
|
package/dist/index.esm.js
CHANGED
|
@@ -266,13 +266,14 @@ class DevtoolsCore {
|
|
|
266
266
|
_changeListeners = new Set();
|
|
267
267
|
/** 観測開始時刻(connect 時の performance.now。未接続は null)。 */
|
|
268
268
|
_observingSince = null;
|
|
269
|
-
/** watch
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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();
|
|
276
277
|
/** 観測開始以降に一度でも attach を観測した宣言キー(attachKeyOf)。
|
|
277
278
|
* live 配線台帳は WeakRef pruning / binding-removed で縮むため、binding
|
|
278
279
|
* カバレッジの attached 判定はこちらで行う(瞬間値で判定すると行の
|
|
@@ -327,8 +328,8 @@ class DevtoolsCore {
|
|
|
327
328
|
this._wiringByPathKey.clear();
|
|
328
329
|
this._wiringEntryByBinding = new WeakMap();
|
|
329
330
|
this._observingSince = null;
|
|
330
|
-
this.
|
|
331
|
-
this.
|
|
331
|
+
this._watchFiredStats.clear();
|
|
332
|
+
this._tokenEmitStats.clear();
|
|
332
333
|
this._everAttachedKeys.clear();
|
|
333
334
|
this._notify("sources");
|
|
334
335
|
this._notify("roster");
|
|
@@ -448,14 +449,24 @@ class DevtoolsCore {
|
|
|
448
449
|
* 配線カバレッジ(§4): 宣言面(watchPaths / token 宣言 / canonical declared)と
|
|
449
450
|
* 実測面(watch-fired・token-emit の台帳・live 配線台帳)の突合。
|
|
450
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
|
+
* 持たないため絞り込みの対象外。
|
|
451
459
|
*/
|
|
452
|
-
getCoverageReport() {
|
|
460
|
+
getCoverageReport(stateElement) {
|
|
453
461
|
const out = [];
|
|
454
462
|
for (const entry of this.getRoster()) {
|
|
455
463
|
const summary = entry.summary;
|
|
456
|
-
|
|
464
|
+
if (stateElement !== undefined && summary.element !== stateElement) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
// --- watch: 宣言(watchPaths)× 実測(watch-fired 台帳・自ツリー + 旧 payload 合算) ---
|
|
457
468
|
for (const path of summary.watchPaths ?? []) {
|
|
458
|
-
const count = this.
|
|
469
|
+
const count = this._measuredFor(this._watchFiredStats, pathKeyOf(path), summary.element)?.count ?? 0;
|
|
459
470
|
if (count > 0) {
|
|
460
471
|
out.push({ kind: "watch", name: path, status: "fired", count, note: null });
|
|
461
472
|
continue;
|
|
@@ -494,10 +505,10 @@ class DevtoolsCore {
|
|
|
494
505
|
// subscriberCount 0 = 空撃ち(§4): 全 emit が空撃ちなら emitted-unheard として
|
|
495
506
|
// 警告する(emit 側だけ配線されて受け手が一つも居ない構成ミスの検出)。 ---
|
|
496
507
|
for (const name of summary.commandTokenNames) {
|
|
497
|
-
out.push(tokenCoverageOf("command", name, this.
|
|
508
|
+
out.push(tokenCoverageOf("command", name, this._measuredFor(this._tokenEmitStats, tokenKeyOf("command", name), summary.element)));
|
|
498
509
|
}
|
|
499
510
|
for (const name of summary.eventTokenNames) {
|
|
500
|
-
out.push(tokenCoverageOf("eventToken", name, this.
|
|
511
|
+
out.push(tokenCoverageOf("eventToken", name, this._measuredFor(this._tokenEmitStats, tokenKeyOf("event", name), summary.element)));
|
|
501
512
|
}
|
|
502
513
|
}
|
|
503
514
|
// --- binding: canonical declared(宣言)× live 配線台帳(実測)。canonical が
|
|
@@ -538,6 +549,18 @@ class DevtoolsCore {
|
|
|
538
549
|
}
|
|
539
550
|
return source.keys(entry.rootNode);
|
|
540
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);
|
|
563
|
+
}
|
|
541
564
|
readValue(entry, path, indexes) {
|
|
542
565
|
const source = this._sources.get(entry.sourceId);
|
|
543
566
|
if (source === undefined) {
|
|
@@ -574,6 +597,60 @@ class DevtoolsCore {
|
|
|
574
597
|
}
|
|
575
598
|
this._roster.set(source.id, entries);
|
|
576
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
|
+
}
|
|
577
654
|
_collectAlive(set) {
|
|
578
655
|
const alive = [];
|
|
579
656
|
for (const entry of set) {
|
|
@@ -726,20 +803,13 @@ class DevtoolsCore {
|
|
|
726
803
|
return;
|
|
727
804
|
}
|
|
728
805
|
case "state:token-emit": {
|
|
729
|
-
// カバレッジの実測面: emit
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
stat.count += 1;
|
|
740
|
-
if (event.subscriberCount === 0) {
|
|
741
|
-
stat.zeroSubscriberCount += 1;
|
|
742
|
-
}
|
|
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;
|
|
743
813
|
}
|
|
744
814
|
this._notify("coverage");
|
|
745
815
|
this._appendTimeline({
|
|
@@ -753,8 +823,8 @@ class DevtoolsCore {
|
|
|
753
823
|
}
|
|
754
824
|
case "state:watch-fired": {
|
|
755
825
|
// timeline 行にはしない(発火は高頻度になりうる活動でありカバレッジが受け皿)。
|
|
756
|
-
|
|
757
|
-
this.
|
|
826
|
+
// 発火元ツリー(payload の stateElement — protocol v2 追補)別に数える。
|
|
827
|
+
this._statFor(this._watchFiredStats, pathKeyOf(event.path), event.stateElement).count += 1;
|
|
758
828
|
this._notify("coverage");
|
|
759
829
|
return;
|
|
760
830
|
}
|
|
@@ -1056,6 +1126,11 @@ header .spacer { flex: 1; }
|
|
|
1056
1126
|
.notice { color: #efe3a0; padding: 2px 0 6px; }
|
|
1057
1127
|
.notice button { font: inherit; margin-left: 6px; cursor: pointer; }
|
|
1058
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; }
|
|
1059
1134
|
.wiring-tabs { padding: 0 0 4px; }
|
|
1060
1135
|
.wiring-tabs button { font: inherit; cursor: pointer; margin-right: 4px; opacity: 0.6; }
|
|
1061
1136
|
.wiring-tabs button.active { opacity: 1; font-weight: bold; }
|
|
@@ -1437,11 +1512,73 @@ class WcsDevtools extends HTMLElement {
|
|
|
1437
1512
|
const keys = core.keysOf(selected);
|
|
1438
1513
|
if (keys.length === 0) {
|
|
1439
1514
|
body.append(this._emptyRow("no readable keys (runtime without keys() API?)"));
|
|
1515
|
+
// keys が読めないランタイムでも overlays は独立に読める可能性があるため出す
|
|
1516
|
+
this._renderOverlaysSection(body, selected);
|
|
1440
1517
|
return;
|
|
1441
1518
|
}
|
|
1442
1519
|
for (const key of keys) {
|
|
1443
1520
|
this._renderTreeNode(body, selected, { path: key, indexes: [] }, key, 0);
|
|
1444
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;
|
|
1445
1582
|
}
|
|
1446
1583
|
_renderTreeNode(container, entry, ref, label, depth) {
|
|
1447
1584
|
const core = this._core;
|
|
@@ -1645,7 +1782,10 @@ class WcsDevtools extends HTMLElement {
|
|
|
1645
1782
|
? "not observing"
|
|
1646
1783
|
: `observing since ${sinceLabel} (declared vs measured after this point)`;
|
|
1647
1784
|
body.append(info);
|
|
1648
|
-
|
|
1785
|
+
// 選択中のツリーでスコープ(複数ツリーの同名 watch パス / token 名を混ぜない —
|
|
1786
|
+
// wiring のパス文脈と同じ流儀。protocol v2 追補のツリー識別が実測面を分ける)
|
|
1787
|
+
const selected = this._selectedRoster();
|
|
1788
|
+
const report = core.getCoverageReport(selected?.summary.element);
|
|
1649
1789
|
if (report.length === 0) {
|
|
1650
1790
|
body.append(this._emptyRow("nothing declared to cover"));
|
|
1651
1791
|
return;
|