wowbagger 0.1.0-alpha.9 → 0.5.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 +509 -0
- package/README.md +272 -136
- package/docs/adapter-contract.md +1 -1
- package/docs/host-contract.md +7 -1
- package/docs/mutation-contract.md +354 -82
- package/docs/work-claim-contract.md +466 -88
- package/package.json +2 -2
- package/schemas/core-capabilities-response.json +1 -1
- package/schemas/core-envelope.json +4 -3
- package/schemas/index.json +18 -0
- package/schemas/ledger-repair-proposal.json +170 -0
- package/schemas/ledger-repair-request.json +61 -0
- package/schemas/ledger-repair-response.json +90 -0
- package/schemas/report-config-v1.json +4 -0
- package/schemas/report-config-v2.json +5 -0
- package/skills/wowbagger/SKILL.md +242 -59
- package/src/adapter/core-probe.js +3 -4
- package/src/adapter/process-outcome.js +8 -1
- package/src/claim-capabilities.js +3 -3
- package/src/claim-coordinator.js +61 -12
- package/src/claim-journal.js +212 -7
- package/src/claim-prospective.js +1 -28
- package/src/claim-publication.js +412 -78
- package/src/claim-request.js +9 -0
- package/src/claim-store.js +9 -4
- package/src/cli.js +302 -56
- package/src/extensions.js +1 -0
- package/src/git-autocommit.js +106 -43
- package/src/git-reconciliation.js +74 -19
- package/src/git-worktrees.js +73 -0
- package/src/instrumentation.js +1 -0
- package/src/launch.js +2 -2
- package/src/ledger-repair.js +1170 -0
- package/src/mutation.js +73 -15
- package/src/reconciliation-classifier.js +117 -0
- package/src/report-evidence.js +158 -41
- package/src/report-graph.js +201 -73
- package/src/report-html.js +358 -155
- package/src/report-impact.js +106 -0
- package/src/report-selection.js +97 -0
- package/src/report-sequencing.js +4 -4
- package/src/report-svg.js +74 -20
- package/src/report-view.js +14 -1
- package/src/report.js +109 -23
- package/src/version-drift.js +98 -0
- package/src/worktree-identity.js +165 -0
package/src/report-graph.js
CHANGED
|
@@ -66,6 +66,7 @@ export function buildGraphModel(model) {
|
|
|
66
66
|
asOf: model.asOf,
|
|
67
67
|
nodes,
|
|
68
68
|
links: buildLinks([...model.items, ...model.terminalItems], new Set(nodes.map((node) => node.id))),
|
|
69
|
+
impactById: model.impactById ?? {},
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -174,12 +175,10 @@ export function graphStyleSource() {
|
|
|
174
175
|
.join('');
|
|
175
176
|
return `${bandRules}
|
|
176
177
|
#graph-stage{position:relative;height:min(66vh,620px);border-radius:11px;background:#080b0f;overflow:hidden}
|
|
177
|
-
|
|
178
|
-
.graph-
|
|
179
|
-
.graph-
|
|
180
|
-
.graph-
|
|
181
|
-
.graph-filter-actions button{padding:7px 10px}
|
|
182
|
-
.graph-filter-actions .result-count{margin-right:auto}
|
|
178
|
+
#graph-node-count{margin:12px 0 0}
|
|
179
|
+
.graph-actions{display:flex;flex-wrap:wrap;gap:8px;margin:9px 0 0}
|
|
180
|
+
.graph-actions button{padding:6px 10px}
|
|
181
|
+
#graph-card .graph-actions button{border-color:#3b4c5e;background:#22303d;color:#e6edf3}
|
|
183
182
|
#graph-empty{margin:12px 0 0;border-left:3px solid var(--navy);background:#eef1f4;padding:10px 13px}
|
|
184
183
|
#graph-canvas{position:absolute;inset:0}
|
|
185
184
|
#graph-labels{position:absolute;inset:0;pointer-events:none}
|
|
@@ -214,9 +213,25 @@ export function graphStyleSource() {
|
|
|
214
213
|
|
|
215
214
|
// The roster is not a fallback bolted on for the WebGL-less reader; it is where
|
|
216
215
|
// the graph's decision-relevant content lives. The 3D view adds shape and
|
|
217
|
-
// adjacency to it and holds nothing the roster does not already say.
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
// adjacency to it and holds nothing the roster does not already say. Each row
|
|
217
|
+
// carries the same actions the graph card offers: canonical details through
|
|
218
|
+
// inspectItem, and labelled drilldowns from impactById. Only nonempty impact
|
|
219
|
+
// sets become buttons, so a terminal node offers details alone.
|
|
220
|
+
function renderGraphActions(node, impactById) {
|
|
221
|
+
const impact = impactById[node.id] ?? {};
|
|
222
|
+
const downstreamIds = Array.isArray(impact.downstreamIds) ? impact.downstreamIds : [];
|
|
223
|
+
const readyIds = Array.isArray(impact.readyIfDoneIds) ? impact.readyIfDoneIds : [];
|
|
224
|
+
let buttons = `<button type="button" data-inspect="${escapeHtml(node.id)}">Details</button>`;
|
|
225
|
+
if (downstreamIds.length > 0) {
|
|
226
|
+
buttons += `<button type="button" data-downstream="${escapeHtml(node.id)}">Downstream (${downstreamIds.length})</button>`;
|
|
227
|
+
}
|
|
228
|
+
if (readyIds.length > 0) {
|
|
229
|
+
buttons += `<button type="button" data-ready="${escapeHtml(node.id)}">Ready if done (${readyIds.length})</button>`;
|
|
230
|
+
}
|
|
231
|
+
return `<p class="graph-actions">${buttons}</p>`;
|
|
232
|
+
}
|
|
233
|
+
function renderRoster(nodes, impactById = {}) {
|
|
234
|
+
return `<details class="graph-fallback"><summary>Every node, without the graph</summary><ol class="graph-roster">${nodes.map((node) => `<li class="graph-band-${escapeHtml(node.band)}" data-node-id="${escapeHtml(node.id)}" data-node-status="${escapeHtml(node.status)}"><p class="graph-head"><span class="graph-handle">${escapeHtml(node.handle)}</span><span class="graph-title">${escapeHtml(node.title)}</span><span class="graph-meta">${escapeHtml(BAND_LABELS[node.band] ?? node.band)} · ${escapeHtml(node.status)} · age ${escapeHtml(node.ageDays)}d · unblocks ${escapeHtml(node.leverage)}</span></p><p class="graph-why">${node.reasons.map((reason) => `<span class="graph-reason">${escapeHtml(reason.label)}</span>`).join('')}</p>${renderGraphActions(node, impactById)}</li>`).join('')}</ol></details>`;
|
|
220
235
|
}
|
|
221
236
|
|
|
222
237
|
function renderLegend() {
|
|
@@ -225,20 +240,6 @@ function renderLegend() {
|
|
|
225
240
|
.join('');
|
|
226
241
|
return `<div id="graph-legend">${bands}<span class="graph-key graph-key-edge graph-band-depends">Unblocks (depends_on)</span><span class="graph-key graph-key-edge graph-key-edge-parent graph-band-parent">Parent to child</span></div>`;
|
|
227
242
|
}
|
|
228
|
-
|
|
229
|
-
// The filter offers the lifecycle statuses the ledger actually holds, each
|
|
230
|
-
// carrying how many nodes answer to it, and starts with all of them selected:
|
|
231
|
-
// the graph's own default is the whole ledger. Select all and Clear are the two
|
|
232
|
-
// moves a chip strip cannot make on its own.
|
|
233
|
-
function renderStatusFilter(nodes) {
|
|
234
|
-
const statuses = [...new Set(nodes.map((node) => node.status))].sort();
|
|
235
|
-
const chips = statuses.map((status) => {
|
|
236
|
-
const count = nodes.filter((node) => node.status === status).length;
|
|
237
|
-
return `<label class="chip"><input type="checkbox" class="graph-status" value="${escapeHtml(status)}" checked><span class="chip-text">${escapeHtml(status)}</span> <span class="chip-count">${count}</span></label>`;
|
|
238
|
-
}).join('');
|
|
239
|
-
return `<div id="graph-filter" class="graph-filter"><fieldset class="facet-group" data-group="graph-status"><legend>Status</legend><div class="chips">${chips}</div></fieldset><div class="graph-filter-actions"><p id="graph-node-count" class="result-count" role="status" aria-live="polite">Showing ${nodes.length} of ${nodes.length} ${nodes.length === 1 ? 'node' : 'nodes'}</p><span class="graph-filter-buttons"><button type="button" id="graph-status-all">Select all</button><button type="button" id="graph-status-clear">Clear</button></span></div></div>`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
243
|
// The empty state has to name what emptied the graph. A reader who cleared
|
|
243
244
|
// every status did that, and selecting one undoes it. A named view that matched
|
|
244
245
|
// nothing was never narrowed by anybody, and there is no status for that reader
|
|
@@ -250,36 +251,58 @@ function renderGraphEmptyState(nodes, view) {
|
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
export function graphSection(model, manifest, view = null) {
|
|
254
|
+
const nodes = model.nodes;
|
|
253
255
|
return `<section id="graph" class="panel"><div class="section-heading"><div><p class="eyebrow">Dependencies</p><h2>Ledger graph</h2></div><p class="muted">Every item as a node, sized by how much it unblocks. Edges run from a prerequisite or parent to the item it releases.</p></div>
|
|
254
|
-
${
|
|
256
|
+
<p id="graph-node-count" class="result-count" role="status" aria-live="polite">Showing ${nodes.length} of ${nodes.length} ${nodes.length === 1 ? 'node' : 'nodes'}</p>
|
|
255
257
|
<div id="graph-stage"><div id="graph-canvas"></div><div id="graph-labels" aria-hidden="true"></div><aside id="graph-card" hidden></aside><p id="graph-hint">Drag to orbit · scroll to zoom · hover or click a node</p></div>
|
|
256
258
|
<p id="graph-nowebgl" hidden>This browser has no WebGL, so the graph cannot draw. Nothing is lost: every node, its status, its age, and the reasons that place it are listed below, and the graph only adds the shape of the dependencies between them.</p>
|
|
257
|
-
${renderGraphEmptyState(
|
|
259
|
+
${renderGraphEmptyState(nodes, view)}
|
|
258
260
|
${renderLegend()}
|
|
259
|
-
${renderRoster(model.
|
|
261
|
+
${renderRoster(nodes, model.impactById ?? {})}
|
|
260
262
|
<p class="muted">Rendered by ${escapeHtml(manifest.package)} ${escapeHtml(manifest.version)}, vendored and checksummed in this repository and inlined here. This report fetches nothing.</p></section>`;
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
export function graphClientSource(model) {
|
|
264
266
|
return `'use strict';
|
|
265
|
-
var GRAPH_MODEL=${scriptJson(model)};
|
|
267
|
+
var GRAPH_MODEL=${scriptJson({ asOf: model.asOf, nodes: model.nodes, links: model.links })};
|
|
266
268
|
var GRAPH_COLORS=${scriptJson(BAND_COLORS)};
|
|
267
269
|
var graphStage=document.getElementById('graph-stage');
|
|
268
270
|
var graphNotice=document.getElementById('graph-nowebgl');
|
|
269
271
|
var graphEmpty=document.getElementById('graph-empty');
|
|
270
272
|
var graphCount=document.getElementById('graph-node-count');
|
|
273
|
+
var graphEmptyCopy=graphEmpty?graphEmpty.textContent:'';
|
|
271
274
|
var graphFallback=document.querySelector('.graph-fallback');
|
|
272
|
-
var
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
var graphSection=document.getElementById('section-dependencies');
|
|
276
|
+
var graphNav=document.getElementById('nav-dependencies');
|
|
277
|
+
var graphReducedMotion=false;
|
|
278
|
+
try{graphReducedMotion=!!(window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches);}catch(error){}
|
|
279
|
+
var graphStarted=false;
|
|
280
|
+
var graphDisposed=false;
|
|
281
|
+
var graphInstance=null;
|
|
282
|
+
var graphUnsubscribe=null;
|
|
283
|
+
var graphLabelRaf=0;
|
|
284
|
+
var graphRosterRows=Array.prototype.slice.call(document.querySelectorAll('[data-node-id]'));
|
|
285
|
+
var graphNodesById=Object.create(null);
|
|
286
|
+
GRAPH_MODEL.nodes.forEach(function(node){graphNodesById[node.id]=node;});
|
|
287
|
+
// The shared report scope owns selection: the controller installed by
|
|
288
|
+
// reportClientSource publishes matching items, and the graph draws the induced
|
|
289
|
+
// subgraph without recomputing readiness from an incomplete picture.
|
|
290
|
+
var graphReport=window.wowbaggerReport||null;
|
|
291
|
+
// The stage hands back the one function that redraws it. A scope that arrives
|
|
292
|
+
// before the stage exists waits here until the first sizing.
|
|
276
293
|
var graphDraw=null;
|
|
294
|
+
var graphPendingVisible=null;
|
|
277
295
|
var graphShownId=null;
|
|
296
|
+
// Probed once: every probe opens a WebGL context, and a retry loop that probes
|
|
297
|
+
// each frame exhausts the browser's context budget and evicts the graph's own.
|
|
298
|
+
var graphWebglProbe=null;
|
|
278
299
|
function graphWebglAvailable(){
|
|
300
|
+
if(graphWebglProbe!==null)return graphWebglProbe;
|
|
279
301
|
try{
|
|
280
302
|
var probe=document.createElement('canvas');
|
|
281
|
-
|
|
282
|
-
}catch(error){
|
|
303
|
+
graphWebglProbe=!!(window.WebGLRenderingContext&&(probe.getContext('webgl2')||probe.getContext('webgl')));
|
|
304
|
+
}catch(error){graphWebglProbe=false;}
|
|
305
|
+
return graphWebglProbe;
|
|
283
306
|
}
|
|
284
307
|
function graphBandColor(node){return GRAPH_COLORS[node.band]||'#94a3b8';}
|
|
285
308
|
function graphRenderCard(card,node){
|
|
@@ -308,52 +331,158 @@ function graphRenderCard(card,node){
|
|
|
308
331
|
why.append(chip);
|
|
309
332
|
});
|
|
310
333
|
card.append(handle,title,facts,why);
|
|
334
|
+
var actions=document.createElement('p');
|
|
335
|
+
actions.className='graph-actions';
|
|
336
|
+
var details=document.createElement('button');
|
|
337
|
+
details.type='button';
|
|
338
|
+
details.textContent='Details';
|
|
339
|
+
details.addEventListener('click',function(){graphInspect(node.id);});
|
|
340
|
+
actions.append(details);
|
|
341
|
+
var impact=graphImpactLists(node.id);
|
|
342
|
+
if(impact.downstreamIds.length>0){
|
|
343
|
+
var downstream=document.createElement('button');
|
|
344
|
+
downstream.type='button';
|
|
345
|
+
downstream.textContent='Downstream ('+impact.downstreamIds.length+')';
|
|
346
|
+
downstream.addEventListener('click',function(){graphShowImpact(node.id,'downstream');});
|
|
347
|
+
actions.append(downstream);
|
|
348
|
+
}
|
|
349
|
+
if(impact.readyIfDoneIds.length>0){
|
|
350
|
+
var unlocks=document.createElement('button');
|
|
351
|
+
unlocks.type='button';
|
|
352
|
+
unlocks.textContent='Ready if done ('+impact.readyIfDoneIds.length+')';
|
|
353
|
+
unlocks.addEventListener('click',function(){graphShowImpact(node.id,'ready');});
|
|
354
|
+
actions.append(unlocks);
|
|
355
|
+
}
|
|
356
|
+
card.append(actions);
|
|
311
357
|
card.hidden=false;
|
|
312
358
|
graphShownId=node.id;
|
|
313
359
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return chosen;
|
|
320
|
-
}
|
|
321
|
-
// One selection drives the stage, the roster, and the count together, so the
|
|
322
|
-
// three can never disagree about which part of the ledger is on screen.
|
|
323
|
-
function graphApplyFilter(){
|
|
324
|
-
var chosen=graphSelectedStatuses();
|
|
360
|
+
// One scope drives the stage, the roster, and the count together, so the three
|
|
361
|
+
// can never disagree about which part of the ledger is on screen. Bands and
|
|
362
|
+
// reasons come from the complete-ledger model, so hiding a prerequisite never
|
|
363
|
+
// recomputes the items it holds back.
|
|
364
|
+
function graphApplyScope(items){
|
|
325
365
|
var visible=Object.create(null);
|
|
326
366
|
var shown=0;
|
|
327
|
-
|
|
328
|
-
if(
|
|
329
|
-
|
|
330
|
-
|
|
367
|
+
(items||[]).forEach(function(item){
|
|
368
|
+
if(item&&graphNodesById[item.id]!==undefined&&visible[item.id]!==true){
|
|
369
|
+
visible[item.id]=true;
|
|
370
|
+
shown+=1;
|
|
371
|
+
}
|
|
331
372
|
});
|
|
332
373
|
graphRosterRows.forEach(function(row){
|
|
333
|
-
|
|
374
|
+
var id=row.getAttribute('data-node-id');
|
|
375
|
+
row.hidden=!(id!==null&&visible[id]===true);
|
|
334
376
|
});
|
|
335
377
|
var total=GRAPH_MODEL.nodes.length;
|
|
336
|
-
graphCount.textContent='Showing '+shown+' of '+total+(total===1?' node':' nodes');
|
|
337
|
-
graphEmpty
|
|
378
|
+
if(graphCount)graphCount.textContent='Showing '+shown+' of '+total+(total===1?' node':' nodes');
|
|
379
|
+
if(graphEmpty){
|
|
380
|
+
if(total===0){graphEmpty.hidden=false;}
|
|
381
|
+
else if(shown===0){graphEmpty.textContent='No items match the current scope, so the graph has nothing to draw.';graphEmpty.hidden=false;}
|
|
382
|
+
else{graphEmpty.textContent=graphEmptyCopy;graphEmpty.hidden=true;}
|
|
383
|
+
}
|
|
338
384
|
if(graphDraw)graphDraw(visible);
|
|
385
|
+
else graphPendingVisible=visible;
|
|
339
386
|
}
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
387
|
+
function graphMount(){return document.getElementById('graph-canvas');}
|
|
388
|
+
// Impact actions read the frozen controller map, never a recomputed graph: the
|
|
389
|
+
// labels carry the same downstream and ready-if-done sets the Items detail
|
|
390
|
+
// shows, with their distinct counts.
|
|
391
|
+
function graphImpactLists(id){
|
|
392
|
+
var entry=null;
|
|
393
|
+
try{entry=graphReport&&graphReport.impactById?graphReport.impactById[id]:null;}catch(error){entry=null;}
|
|
394
|
+
var downstream=entry&&Array.isArray(entry.downstreamIds)?entry.downstreamIds.slice():[];
|
|
395
|
+
var ready=entry&&Array.isArray(entry.readyIfDoneIds)?entry.readyIfDoneIds.slice():[];
|
|
396
|
+
return {downstreamIds:downstream,readyIfDoneIds:ready};
|
|
345
397
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
398
|
+
function graphNodeHandle(id){var node=graphNodesById[id];return node?node.handle:id;}
|
|
399
|
+
function graphInspect(id){
|
|
400
|
+
try{if(graphReport&&typeof graphReport.inspectItem==='function')return graphReport.inspectItem(id);}catch(error){}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
function graphShowImpact(id,kind){
|
|
404
|
+
if(!graphReport||typeof graphReport.showItems!=='function')return;
|
|
405
|
+
var lists=graphImpactLists(id);
|
|
406
|
+
var handle=graphNodeHandle(id);
|
|
407
|
+
if(kind==='downstream'&&lists.downstreamIds.length>0)graphReport.showItems({label:'Downstream of '+handle+' ('+lists.downstreamIds.length+')',itemIds:lists.downstreamIds});
|
|
408
|
+
if(kind==='ready'&&lists.readyIfDoneIds.length>0)graphReport.showItems({label:'Ready if '+handle+' is done ('+lists.readyIfDoneIds.length+')',itemIds:lists.readyIfDoneIds});
|
|
409
|
+
}
|
|
410
|
+
function graphWireActions(root){
|
|
411
|
+
Array.prototype.slice.call(root.querySelectorAll('[data-inspect]')).forEach(function(button){
|
|
412
|
+
button.addEventListener('click',function(){graphInspect(button.getAttribute('data-inspect'));});
|
|
413
|
+
});
|
|
414
|
+
Array.prototype.slice.call(root.querySelectorAll('[data-downstream]')).forEach(function(button){
|
|
415
|
+
button.addEventListener('click',function(){graphShowImpact(button.getAttribute('data-downstream'),'downstream');});
|
|
416
|
+
});
|
|
417
|
+
Array.prototype.slice.call(root.querySelectorAll('[data-ready]')).forEach(function(button){
|
|
418
|
+
button.addEventListener('click',function(){graphShowImpact(button.getAttribute('data-ready'),'ready');});
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
graphWireActions(document);
|
|
422
|
+
function graphSectionHidden(){return !!(graphSection&&graphSection.hidden===true);}
|
|
423
|
+
function graphMeasurable(){var mount=graphMount();return !!(mount&&mount.clientWidth>0&&mount.clientHeight>0);}
|
|
424
|
+
function graphCanDraw(){return !!(window.ForceGraph3D&&graphWebglAvailable());}
|
|
425
|
+
// The 1,744-item reference built one canvas while Dependencies was still
|
|
426
|
+
// hidden: a hidden container measures zero, so the first sizing waits for a
|
|
427
|
+
// visible section with measurable dimensions. Repeated visits reuse the one
|
|
428
|
+
// canvas instead of starting the layout again.
|
|
429
|
+
function graphEnsureStarted(){
|
|
430
|
+
if(graphStarted||graphDisposed)return;
|
|
431
|
+
if(!graphCanDraw()||GRAPH_MODEL.nodes.length===0)return;
|
|
432
|
+
if(graphSectionHidden())return;
|
|
433
|
+
if(!graphMeasurable()){
|
|
434
|
+
if(window.requestAnimationFrame)window.requestAnimationFrame(function(){graphEnsureStarted();});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
351
437
|
graphStart();
|
|
438
|
+
graphStarted=true;
|
|
439
|
+
if(graphPendingVisible&&graphDraw)graphDraw(graphPendingVisible);
|
|
440
|
+
}
|
|
441
|
+
function graphNoteVisibility(){
|
|
442
|
+
if(!graphStarted||!graphInstance)return;
|
|
443
|
+
if(graphSectionHidden()){
|
|
444
|
+
if(graphInstance&&typeof graphInstance.pauseAnimation==='function')graphInstance.pauseAnimation();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
var mount=graphMount();
|
|
448
|
+
if(mount&&graphInstance&&typeof graphInstance.width==='function'){
|
|
449
|
+
try{graphInstance.width(mount.clientWidth).height(mount.clientHeight);}catch(error){}
|
|
450
|
+
}
|
|
451
|
+
if(graphInstance&&typeof graphInstance.resumeAnimation==='function')graphInstance.resumeAnimation();
|
|
452
|
+
}
|
|
453
|
+
// Disposal releases the layout, its label loop, and the scope subscription, so
|
|
454
|
+
// leaving the document does not leave the force layout ticking.
|
|
455
|
+
function graphDispose(){
|
|
456
|
+
if(graphDisposed)return;
|
|
457
|
+
graphDisposed=true;
|
|
458
|
+
if(typeof graphUnsubscribe==='function'){try{graphUnsubscribe();}catch(error){}graphUnsubscribe=null;}
|
|
459
|
+
if(window.cancelAnimationFrame&&graphLabelRaf){try{window.cancelAnimationFrame(graphLabelRaf);}catch(error){}graphLabelRaf=0;}
|
|
460
|
+
if(graphInstance&&typeof graphInstance._destructor==='function'){try{graphInstance._destructor();}catch(error){}}
|
|
461
|
+
graphInstance=null;
|
|
462
|
+
graphDraw=null;
|
|
463
|
+
}
|
|
464
|
+
if(!graphCanDraw()){
|
|
465
|
+
if(graphStage)graphStage.hidden=true;
|
|
466
|
+
if(graphNotice)graphNotice.hidden=false;
|
|
467
|
+
if(graphFallback)graphFallback.open=true;
|
|
468
|
+
}
|
|
469
|
+
if(graphReport&&typeof graphReport.subscribeScope==='function'){
|
|
470
|
+
graphUnsubscribe=graphReport.subscribeScope(function(items){graphApplyScope(items||[]);graphEnsureStarted();graphNoteVisibility();});
|
|
471
|
+
}else{
|
|
472
|
+
graphApplyScope(GRAPH_MODEL.nodes.map(function(node){return {id:node.id};}));
|
|
473
|
+
graphEnsureStarted();
|
|
474
|
+
}
|
|
475
|
+
if(graphNav)graphNav.addEventListener('click',function(){graphEnsureStarted();graphNoteVisibility();});
|
|
476
|
+
if(graphSection&&window.MutationObserver){
|
|
477
|
+
try{
|
|
478
|
+
new window.MutationObserver(function(){graphEnsureStarted();graphNoteVisibility();}).observe(graphSection,{attributes:true,attributeFilter:['hidden']});
|
|
479
|
+
}catch(error){}
|
|
480
|
+
}
|
|
481
|
+
if(window.addEventListener){
|
|
482
|
+
window.addEventListener('resize',function(){graphEnsureStarted();graphNoteVisibility();});
|
|
483
|
+
window.addEventListener('pagehide',function(){graphDispose();});
|
|
484
|
+
window.addEventListener('unload',function(){graphDispose();});
|
|
352
485
|
}
|
|
353
|
-
graphStatusInputs.forEach(function(input){input.addEventListener('change',graphApplyFilter);});
|
|
354
|
-
document.getElementById('graph-status-all').addEventListener('click',function(){graphSetEveryStatus(true);});
|
|
355
|
-
document.getElementById('graph-status-clear').addEventListener('click',function(){graphSetEveryStatus(false);});
|
|
356
|
-
graphApplyFilter();
|
|
357
486
|
function graphStart(){
|
|
358
487
|
var mount=document.getElementById('graph-canvas');
|
|
359
488
|
var labelLayer=document.getElementById('graph-labels');
|
|
@@ -381,9 +510,10 @@ function graphStart(){
|
|
|
381
510
|
.linkDirectionalArrowLength(function(link){return link.kind==='parent'?0:8;})
|
|
382
511
|
.linkDirectionalArrowRelPos(1)
|
|
383
512
|
.onNodeHover(function(node){if(node)graphRenderCard(card,node);})
|
|
384
|
-
.onNodeClick(function(node){if(node)graphRenderCard(card,node);})
|
|
513
|
+
.onNodeClick(function(node){if(node){graphRenderCard(card,node);graphInspect(node.id);}})
|
|
385
514
|
.onBackgroundClick(function(){card.hidden=true;graphShownId=null;})
|
|
386
|
-
.cooldownTicks(220);
|
|
515
|
+
.cooldownTicks(graphReducedMotion?0:220);
|
|
516
|
+
graphInstance=graph;
|
|
387
517
|
graph.d3Force('charge').strength(-40);
|
|
388
518
|
var labels=nodes.map(function(node){
|
|
389
519
|
var element=document.createElement('span');
|
|
@@ -397,11 +527,9 @@ function graphStart(){
|
|
|
397
527
|
graph.onEngineStop(function(){
|
|
398
528
|
if(framed||drawn===0)return;
|
|
399
529
|
framed=true;
|
|
400
|
-
graph.zoomToFit(800,20);
|
|
401
|
-
});
|
|
402
|
-
window.addEventListener('resize',function(){
|
|
403
|
-
graph.width(mount.clientWidth).height(mount.clientHeight);
|
|
530
|
+
graph.zoomToFit(graphReducedMotion?0:800,20);
|
|
404
531
|
});
|
|
532
|
+
window.addEventListener('resize',function(){graphNoteVisibility();});
|
|
405
533
|
// A hidden node's edges have nowhere to land, and its label would hang over
|
|
406
534
|
// empty space, so the node, its links, and its label leave together. Handing
|
|
407
535
|
// the graph new data reheats the layout, which is what re-forms the remaining
|
|
@@ -458,8 +586,8 @@ function graphStart(){
|
|
|
458
586
|
placement.element.style.transform='translate('+placement.screen.x.toFixed(1)+'px,'+placement.screen.y.toFixed(1)+'px)';
|
|
459
587
|
placement.element.style.opacity=(1-0.55*((placement.depth-nearest)/span)).toFixed(2);
|
|
460
588
|
}
|
|
461
|
-
window.requestAnimationFrame(graphPlaceLabels);
|
|
589
|
+
if(!graphDisposed)graphLabelRaf=window.requestAnimationFrame(graphPlaceLabels);
|
|
462
590
|
}
|
|
463
|
-
window.requestAnimationFrame(graphPlaceLabels);
|
|
591
|
+
graphLabelRaf=window.requestAnimationFrame(graphPlaceLabels);
|
|
464
592
|
}`;
|
|
465
593
|
}
|