tink-harness 1.9.6 → 1.9.7
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
CHANGED
|
@@ -6,6 +6,17 @@ All notable changes to Tink are tracked here.
|
|
|
6
6
|
|
|
7
7
|
No unreleased changes yet.
|
|
8
8
|
|
|
9
|
+
## [1.9.7] - 2026-06-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- The harness map now looks like a planetary system: usage/evidence/score satellites orbit their harness slowly (28-63s per revolution, alternating directions) along dashed orbit rings, top harnesses get a Saturn-style ring, and a starfield with twinkling stars sits behind the graph.
|
|
14
|
+
- Selecting a node now dims and blurs everything unrelated (focus mode); clicking the background clears the selection and restores the detail panel.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Dragging the map no longer triggers text selection on labels and captions (user-select disabled on the map panel).
|
|
19
|
+
|
|
9
20
|
## [1.9.6] - 2026-06-10
|
|
10
21
|
|
|
11
22
|
### Added
|
package/VERSIONING.md
CHANGED
package/package.json
CHANGED
|
@@ -618,8 +618,7 @@ function buildGraphLayout(summary) {
|
|
|
618
618
|
glow: score >= 50 || radius >= 20
|
|
619
619
|
};
|
|
620
620
|
});
|
|
621
|
-
const
|
|
622
|
-
const virtualEdges = [];
|
|
621
|
+
const orbitSystems = [];
|
|
623
622
|
for (const node of positioned.filter((item) => item.type === 'harness')) {
|
|
624
623
|
const harnessId = shortLabel(node.id);
|
|
625
624
|
const harness = harnessById.get(harnessId);
|
|
@@ -627,38 +626,25 @@ function buildGraphLayout(summary) {
|
|
|
627
626
|
const uses = Number(harness.signals?.uses || 0);
|
|
628
627
|
const evidenceCount = (harness.evidence_handles || []).length;
|
|
629
628
|
const factorCount = (harness.candidate_score?.factors || []).length;
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
radius: satellite.radius,
|
|
647
|
-
color: TYPE_COLORS[satellite.kind] || TYPE_COLORS.unknown,
|
|
648
|
-
glow: satellite.kind === 'score'
|
|
649
|
-
};
|
|
650
|
-
augmented.push(child);
|
|
651
|
-
virtualEdges.push({
|
|
652
|
-
source: node.id,
|
|
653
|
-
target: child.id,
|
|
654
|
-
type: satellite.kind,
|
|
655
|
-
count: 1
|
|
656
|
-
});
|
|
657
|
-
});
|
|
629
|
+
const seed = hashString(node.id);
|
|
630
|
+
const rings = [
|
|
631
|
+
{ kind: 'signal', count: Math.min(10, Math.ceil(uses / 2)), distance: node.radius + 18, dotRadius: 2.4 },
|
|
632
|
+
{ kind: 'evidence', count: Math.min(6, evidenceCount), distance: node.radius + 29, dotRadius: 2.9 },
|
|
633
|
+
{ kind: 'score', count: Math.min(5, factorCount), distance: node.radius + 40, dotRadius: 3.2 }
|
|
634
|
+
]
|
|
635
|
+
.filter((ring) => ring.count > 0)
|
|
636
|
+
.map((ring, ringIndex) => ({
|
|
637
|
+
...ring,
|
|
638
|
+
duration: (28 + ((seed >> (ringIndex * 3)) % 36)).toFixed(0),
|
|
639
|
+
reverse: ringIndex % 2 === 1,
|
|
640
|
+
phase: ((seed >> (ringIndex * 5)) % 628) / 100
|
|
641
|
+
}));
|
|
642
|
+
if (rings.length) {
|
|
643
|
+
orbitSystems.push({ parentId: node.id, x: node.x, y: node.y, rings });
|
|
644
|
+
}
|
|
658
645
|
}
|
|
659
|
-
const byId = new Map(
|
|
660
|
-
const
|
|
661
|
-
const drawnEdges = [...filteredEdges, ...virtualEdges]
|
|
646
|
+
const byId = new Map(positioned.map((node) => [node.id, node]));
|
|
647
|
+
const drawnEdges = getRenderableEdges(edges)
|
|
662
648
|
.map((edge) => ({
|
|
663
649
|
...edge,
|
|
664
650
|
sourceNode: byId.get(edge.source),
|
|
@@ -667,15 +653,26 @@ function buildGraphLayout(summary) {
|
|
|
667
653
|
.filter((edge) => edge.sourceNode && edge.targetNode)
|
|
668
654
|
.slice(0, 240);
|
|
669
655
|
|
|
670
|
-
return { nodes:
|
|
656
|
+
return { nodes: positioned, edges: drawnEdges, orbitSystems };
|
|
671
657
|
}
|
|
672
658
|
|
|
673
659
|
function renderGraphCanvas(summary, copy) {
|
|
674
|
-
const { nodes, edges } = buildGraphLayout(summary);
|
|
660
|
+
const { nodes, edges, orbitSystems } = buildGraphLayout(summary);
|
|
675
661
|
const strongest = nodes
|
|
676
662
|
.filter((node) => node.type === 'harness')
|
|
677
663
|
.sort((a, b) => Number(b.weight || 0) - Number(a.weight || 0))
|
|
678
664
|
.slice(0, 8);
|
|
665
|
+
const stars = Array.from({ length: 110 }, (_, index) => {
|
|
666
|
+
const seed = hashString(`star:${index}`);
|
|
667
|
+
return {
|
|
668
|
+
x: seed % 1090,
|
|
669
|
+
y: (seed >> 4) % 680,
|
|
670
|
+
r: (0.5 + ((seed >> 8) % 12) / 10).toFixed(1),
|
|
671
|
+
opacity: (0.1 + ((seed >> 6) % 45) / 100).toFixed(2),
|
|
672
|
+
twinkle: index % 4 === 0,
|
|
673
|
+
delay: seed % 5000
|
|
674
|
+
};
|
|
675
|
+
});
|
|
679
676
|
const mapTitle = copy.knowledgeGraph || copy.harnessMap || 'Harness map';
|
|
680
677
|
const mapEyebrow = copy.harnessMap && copy.harnessMap !== mapTitle ? copy.harnessMap : '';
|
|
681
678
|
return `
|
|
@@ -700,9 +697,10 @@ function renderGraphCanvas(summary, copy) {
|
|
|
700
697
|
</div>
|
|
701
698
|
<svg class="graph-canvas" viewBox="0 0 1090 680" role="img" aria-label="Harness health graph">
|
|
702
699
|
<defs>
|
|
703
|
-
<radialGradient id="graph-bg-grad" cx="50%" cy="42%" r="
|
|
704
|
-
<stop offset="0%" style="stop-color: #
|
|
705
|
-
<stop offset="
|
|
700
|
+
<radialGradient id="graph-bg-grad" cx="50%" cy="42%" r="80%">
|
|
701
|
+
<stop offset="0%" style="stop-color: #11141C"/>
|
|
702
|
+
<stop offset="60%" style="stop-color: #0A0C12"/>
|
|
703
|
+
<stop offset="100%" style="stop-color: #06070B"/>
|
|
706
704
|
</radialGradient>
|
|
707
705
|
${Object.entries(TYPE_COLORS).map(([type, color]) => `
|
|
708
706
|
<radialGradient id="node-grad-${escapeAttr(type)}" cx="32%" cy="28%" r="78%">
|
|
@@ -713,6 +711,11 @@ function renderGraphCanvas(summary, copy) {
|
|
|
713
711
|
`).join('')}
|
|
714
712
|
</defs>
|
|
715
713
|
<rect class="graph-bg" width="1090" height="680" fill="url(#graph-bg-grad)"/>
|
|
714
|
+
<g class="starfield" aria-hidden="true">
|
|
715
|
+
${stars.map((star) => `
|
|
716
|
+
<circle cx="${star.x}" cy="${star.y}" r="${star.r}" fill="#FFFFFF" opacity="${star.opacity}"${star.twinkle ? ` class="star-twinkle" style="--twinkle-delay: ${star.delay}ms"` : ''}/>
|
|
717
|
+
`).join('')}
|
|
718
|
+
</g>
|
|
716
719
|
<g id="graph-viewport">
|
|
717
720
|
<g class="edges">
|
|
718
721
|
${edges.map((edge, index) => `
|
|
@@ -731,6 +734,22 @@ function renderGraphCanvas(summary, copy) {
|
|
|
731
734
|
/>
|
|
732
735
|
`).join('')}
|
|
733
736
|
</g>
|
|
737
|
+
<g class="orbits" aria-hidden="true">
|
|
738
|
+
${orbitSystems.map((system) => `
|
|
739
|
+
<g class="orbit-system" data-parent="${escapeAttr(system.parentId)}" transform="translate(${system.x.toFixed(1)} ${system.y.toFixed(1)})">
|
|
740
|
+
${system.rings.map((ring) => {
|
|
741
|
+
const dots = Array.from({ length: ring.count }, (_, dotIndex) => {
|
|
742
|
+
const angle = ring.phase + (dotIndex * Math.PI * 2) / ring.count;
|
|
743
|
+
return `<circle class="orbit-dot" cx="${(Math.cos(angle) * ring.distance).toFixed(1)}" cy="${(Math.sin(angle) * ring.distance).toFixed(1)}" r="${ring.dotRadius}" fill="url(#node-grad-${escapeAttr(ring.kind)})"/>`;
|
|
744
|
+
}).join('');
|
|
745
|
+
return `
|
|
746
|
+
<circle class="orbit-ring" cx="0" cy="0" r="${ring.distance.toFixed(1)}"/>
|
|
747
|
+
<g class="orbit-spin${ring.reverse ? ' is-reverse' : ''}" style="--orbit-dur: ${ring.duration}s">${dots}</g>
|
|
748
|
+
`;
|
|
749
|
+
}).join('')}
|
|
750
|
+
</g>
|
|
751
|
+
`).join('')}
|
|
752
|
+
</g>
|
|
734
753
|
<g class="nodes">
|
|
735
754
|
${nodes.map((node, index) => {
|
|
736
755
|
const seed = hashString(node.id);
|
|
@@ -740,6 +759,9 @@ function renderGraphCanvas(summary, copy) {
|
|
|
740
759
|
const floatY = (((seed >> 3) % 7) - 3).toFixed(1);
|
|
741
760
|
return `
|
|
742
761
|
<g class="node-float" style="--float-dur: ${floatDuration}s; --float-delay: ${floatDelay}ms; --float-x: ${floatX}px; --float-y: ${floatY}px">
|
|
762
|
+
${node.type === 'harness' && node.radius >= 15 ? `
|
|
763
|
+
<ellipse class="planet-ring" cx="${node.x.toFixed(1)}" cy="${node.y.toFixed(1)}" rx="${(node.radius * 1.75).toFixed(1)}" ry="${(node.radius * 0.5).toFixed(1)}" transform="rotate(-16 ${node.x.toFixed(1)} ${node.y.toFixed(1)})"/>
|
|
764
|
+
` : ''}
|
|
743
765
|
<circle
|
|
744
766
|
class="graph-node ${node.type === 'harness' ? 'is-interactive' : ''}"
|
|
745
767
|
style="--enter-delay: ${Math.min(index * 9, 1100)}ms"
|
|
@@ -1358,14 +1380,30 @@ function renderScript(harnesses, copy) {
|
|
|
1358
1380
|
const setFilterStatus = (value) => {
|
|
1359
1381
|
if (filterStatus) filterStatus.textContent = value;
|
|
1360
1382
|
};
|
|
1383
|
+
const graphCanvas = document.querySelector('.graph-canvas');
|
|
1384
|
+
const orbitSystems = Array.from(document.querySelectorAll('.orbit-system'));
|
|
1385
|
+
const defaultSelectedPanel = selectedPanel ? selectedPanel.innerHTML : '';
|
|
1386
|
+
function syncOrbits() {
|
|
1387
|
+
const hasSelection = graphCanvas && graphCanvas.classList.contains('has-selection');
|
|
1388
|
+
orbitSystems.forEach((system) => {
|
|
1389
|
+
const parent = nodeById(system.dataset.parent);
|
|
1390
|
+
const hidden = !parent || parent.classList.contains('is-hidden') || parent.classList.contains('is-filtered-out');
|
|
1391
|
+
system.classList.toggle('is-hidden', hidden);
|
|
1392
|
+
const related = parent && (parent.classList.contains('is-selected') || parent.classList.contains('is-related'));
|
|
1393
|
+
system.classList.toggle('is-dimmed', Boolean(hasSelection && !related));
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1361
1396
|
function clearSelection() {
|
|
1362
1397
|
nodes.forEach((item) => item.classList.remove('is-selected', 'is-related'));
|
|
1363
1398
|
edges.forEach((item) => item.classList.remove('is-related'));
|
|
1364
1399
|
cards.forEach((item) => item.classList.remove('is-selected'));
|
|
1400
|
+
if (graphCanvas) graphCanvas.classList.remove('has-selection');
|
|
1401
|
+
syncOrbits();
|
|
1365
1402
|
}
|
|
1366
1403
|
function selectNode(node) {
|
|
1367
1404
|
clearSelection();
|
|
1368
1405
|
node.classList.add('is-selected');
|
|
1406
|
+
if (graphCanvas) graphCanvas.classList.add('has-selection');
|
|
1369
1407
|
const id = node.dataset.nodeId;
|
|
1370
1408
|
const item = byHarnessId.get(id);
|
|
1371
1409
|
edges.forEach((edge) => {
|
|
@@ -1393,6 +1431,7 @@ function renderScript(harnesses, copy) {
|
|
|
1393
1431
|
} else if (selectedPanel) {
|
|
1394
1432
|
selectedPanel.innerHTML = '<p class="eyebrow">' + esc(copy.selected) + '</p><h2>' + esc(node.dataset.nodeLabel) + '</h2><p>' + esc(id) + '</p><dl><div><dt>' + esc(copy.type) + '</dt><dd>' + esc(displayValue(node.dataset.nodeType)) + '</dd></div><div><dt>' + esc(copy.weight) + '</dt><dd>' + esc(node.dataset.nodeWeight) + '</dd></div></dl>';
|
|
1395
1433
|
}
|
|
1434
|
+
syncOrbits();
|
|
1396
1435
|
}
|
|
1397
1436
|
function selectHarness(id) {
|
|
1398
1437
|
const node = nodeById('harness:' + id);
|
|
@@ -1410,7 +1449,9 @@ function renderScript(harnesses, copy) {
|
|
|
1410
1449
|
nodes.forEach((node) => node.classList.toggle('is-hidden', mode === 'core' && node.dataset.core !== 'true'));
|
|
1411
1450
|
const visibleIds = new Set(nodes.filter((node) => !node.classList.contains('is-hidden')).map((node) => node.dataset.nodeId));
|
|
1412
1451
|
edges.forEach((edge) => edge.classList.toggle('is-hidden', mode === 'core' && (!visibleIds.has(edge.dataset.source) || !visibleIds.has(edge.dataset.target))));
|
|
1452
|
+
document.querySelectorAll('.orbit-system').forEach((system) => system.classList.toggle('is-mode-hidden', mode === 'core'));
|
|
1413
1453
|
setStatus(mode === 'core' ? copy.coreMode : copy.showingAll);
|
|
1454
|
+
syncOrbits();
|
|
1414
1455
|
}
|
|
1415
1456
|
function filterRecommendation(value, button) {
|
|
1416
1457
|
const alreadyActive = button.classList.contains('active-filter');
|
|
@@ -1424,6 +1465,7 @@ function renderScript(harnesses, copy) {
|
|
|
1424
1465
|
cards.forEach((card) => card.classList.remove('is-filtered-out'));
|
|
1425
1466
|
setStatus(copy.showingAll);
|
|
1426
1467
|
setFilterStatus(copy.showingAll);
|
|
1468
|
+
syncOrbits();
|
|
1427
1469
|
return;
|
|
1428
1470
|
}
|
|
1429
1471
|
button.classList.add('active-filter');
|
|
@@ -1438,6 +1480,7 @@ function renderScript(harnesses, copy) {
|
|
|
1438
1480
|
const label = recLabelByFilter[value] || value;
|
|
1439
1481
|
setStatus(copy.filteredTo + ': ' + label);
|
|
1440
1482
|
setFilterStatus(copy.filteredTo + ': ' + label);
|
|
1483
|
+
syncOrbits();
|
|
1441
1484
|
}
|
|
1442
1485
|
interactiveNodes.forEach((node) => {
|
|
1443
1486
|
node.addEventListener('click', () => selectNode(node));
|
|
@@ -1520,12 +1563,24 @@ function renderScript(harnesses, copy) {
|
|
|
1520
1563
|
panState.y = event.clientY;
|
|
1521
1564
|
applyView();
|
|
1522
1565
|
});
|
|
1566
|
+
let suppressClick = false;
|
|
1523
1567
|
const endPan = () => {
|
|
1568
|
+
suppressClick = Boolean(panState && panState.moved);
|
|
1524
1569
|
panState = null;
|
|
1525
1570
|
graphSvg.classList.remove('is-panning');
|
|
1526
1571
|
};
|
|
1527
1572
|
graphSvg.addEventListener('pointerup', endPan);
|
|
1528
1573
|
graphSvg.addEventListener('pointercancel', endPan);
|
|
1574
|
+
graphSvg.addEventListener('click', (event) => {
|
|
1575
|
+
if (suppressClick) {
|
|
1576
|
+
suppressClick = false;
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if (event.target.classList.contains('graph-bg') || event.target.closest('.starfield')) {
|
|
1580
|
+
clearSelection();
|
|
1581
|
+
if (selectedPanel && defaultSelectedPanel) selectedPanel.innerHTML = defaultSelectedPanel;
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1529
1584
|
graphSvg.addEventListener('dblclick', (event) => {
|
|
1530
1585
|
event.preventDefault();
|
|
1531
1586
|
resetView();
|
|
@@ -2193,6 +2248,70 @@ function renderStyles() {
|
|
|
2193
2248
|
transition: opacity 160ms ease, transform 160ms ease;
|
|
2194
2249
|
}
|
|
2195
2250
|
|
|
2251
|
+
.map-panel,
|
|
2252
|
+
.graph-canvas,
|
|
2253
|
+
.graph-canvas text {
|
|
2254
|
+
user-select: none;
|
|
2255
|
+
-webkit-user-select: none;
|
|
2256
|
+
-webkit-user-drag: none;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
.star-twinkle {
|
|
2260
|
+
animation: star-twinkle 3.4s ease-in-out var(--twinkle-delay, 0ms) infinite alternate;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
@keyframes star-twinkle {
|
|
2264
|
+
from { opacity: 0.08; }
|
|
2265
|
+
to { opacity: 0.6; }
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
.orbit-ring {
|
|
2269
|
+
fill: none;
|
|
2270
|
+
stroke: var(--text-secondary);
|
|
2271
|
+
stroke-opacity: 0.14;
|
|
2272
|
+
stroke-width: 0.7;
|
|
2273
|
+
stroke-dasharray: 2 5;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
.orbit-spin {
|
|
2277
|
+
animation: orbit-rotate var(--orbit-dur, 40s) linear infinite;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
.orbit-spin.is-reverse { animation-direction: reverse; }
|
|
2281
|
+
|
|
2282
|
+
@keyframes orbit-rotate {
|
|
2283
|
+
to { transform: rotate(360deg); }
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
.orbit-dot { opacity: 0.85; }
|
|
2287
|
+
|
|
2288
|
+
.orbit-system {
|
|
2289
|
+
transition: opacity 260ms ease;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.orbit-system.is-hidden,
|
|
2293
|
+
.orbit-system.is-mode-hidden { opacity: 0; pointer-events: none; }
|
|
2294
|
+
.orbit-system.is-dimmed { opacity: 0.08; }
|
|
2295
|
+
|
|
2296
|
+
.planet-ring {
|
|
2297
|
+
fill: none;
|
|
2298
|
+
stroke: var(--text-secondary);
|
|
2299
|
+
stroke-opacity: 0.4;
|
|
2300
|
+
stroke-width: 1.4;
|
|
2301
|
+
pointer-events: none;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
.graph-canvas.has-selection .graph-node:not(.is-selected):not(.is-related) {
|
|
2305
|
+
opacity: 0.14;
|
|
2306
|
+
filter: blur(1.2px);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
.graph-canvas.has-selection .graph-edge:not(.is-related) { opacity: 0.05; }
|
|
2310
|
+
|
|
2311
|
+
.graph-canvas.has-selection .planet-ring { stroke-opacity: 0.1; }
|
|
2312
|
+
|
|
2313
|
+
.graph-canvas.has-selection .labels text { opacity: 0.18; transition: opacity 220ms ease; }
|
|
2314
|
+
|
|
2196
2315
|
.map-controls-row {
|
|
2197
2316
|
display: flex;
|
|
2198
2317
|
gap: var(--space-2);
|
|
@@ -2300,6 +2419,8 @@ function renderStyles() {
|
|
|
2300
2419
|
.graph-node,
|
|
2301
2420
|
.graph-edge,
|
|
2302
2421
|
.graph-canvas text,
|
|
2422
|
+
.orbit-spin,
|
|
2423
|
+
.star-twinkle,
|
|
2303
2424
|
.page.is-active { animation: none; }
|
|
2304
2425
|
.graph-node { transition: none; }
|
|
2305
2426
|
}
|