tink-harness 1.9.5 → 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,30 @@ 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
|
+
|
|
20
|
+
## [1.9.6] - 2026-06-10
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- The harness map is now fully navigable: wheel zoom toward the cursor (0.4x–5x), drag to pan, +/− and reset controls, and double-click to return to the full view.
|
|
25
|
+
- Nodes render as 3D-style spheres with radial gradients, depth shadows on interactive nodes, and a vignette background for depth.
|
|
26
|
+
- Added a "How to read this map" card to the graph tab's right rail with plain-language explanations of circles, colors, lines, satellites, controls, and edge types.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Reordered the graph right rail: reading guide → selected node → graph overview.
|
|
31
|
+
- Map help text now mentions zoom and drag controls.
|
|
32
|
+
|
|
9
33
|
## [1.9.5] - 2026-06-10
|
|
10
34
|
|
|
11
35
|
### Added
|
package/VERSIONING.md
CHANGED
package/package.json
CHANGED
|
@@ -48,7 +48,7 @@ const COPY = {
|
|
|
48
48
|
heroText: 'Every visible Tink run, rule, memory reference, and harness relationship mapped into one local dashboard. This report only prepares suggestions and never edits reusable state.',
|
|
49
49
|
generated: 'GENERATED',
|
|
50
50
|
harnessMap: 'HARNESS MAP',
|
|
51
|
-
mapHelp: 'Harnesses, rules, memory, and stages are mapped from visible Tink records.
|
|
51
|
+
mapHelp: 'Harnesses, rules, memory, and stages are mapped from visible Tink records. Scroll to zoom, drag to move, click a node to inspect it.',
|
|
52
52
|
graphControls: 'Graph controls',
|
|
53
53
|
full: 'Full',
|
|
54
54
|
core: 'Core',
|
|
@@ -146,6 +146,30 @@ const COPY = {
|
|
|
146
146
|
runWindow: 'Run window',
|
|
147
147
|
totalRuns: 'Runs',
|
|
148
148
|
refCount: 'References',
|
|
149
|
+
zoomIn: 'Zoom in',
|
|
150
|
+
zoomOut: 'Zoom out',
|
|
151
|
+
zoomReset: 'Reset view',
|
|
152
|
+
mapGuideEyebrow: 'HOW TO READ',
|
|
153
|
+
mapGuideTitle: 'What is this map?',
|
|
154
|
+
mapGuideText: 'Each circle is something Tink knows about: a harness, a rule it loads, or a memory file it reads. The map is drawn only from visible local records.',
|
|
155
|
+
guideItems: [
|
|
156
|
+
['Big circle', 'The more a harness is used, the bigger its circle.'],
|
|
157
|
+
['Color', 'Blue circles are harnesses; gray ones are rules, memory, and stages.'],
|
|
158
|
+
['Line', 'A line means "works together": a harness using a rule, reading memory, or leading to a next step.'],
|
|
159
|
+
['Small dots', 'Tiny satellites around a harness are its usage, evidence, and score signals.']
|
|
160
|
+
],
|
|
161
|
+
controlItems: [
|
|
162
|
+
['Wheel / + −', 'Zoom in and out'],
|
|
163
|
+
['Drag', 'Move around the map'],
|
|
164
|
+
['Double-click', 'Back to full view'],
|
|
165
|
+
['Click a circle', 'See its details below']
|
|
166
|
+
],
|
|
167
|
+
relationTitle: 'What the lines mean',
|
|
168
|
+
relationItems: [
|
|
169
|
+
['uses_rule', 'This harness loads that rule'],
|
|
170
|
+
['uses_memory', 'This harness reads that memory file'],
|
|
171
|
+
['sequence', 'This harness is usually followed by that step']
|
|
172
|
+
],
|
|
149
173
|
groups: [
|
|
150
174
|
['keep', 'Healthy harnesses', 'Ready to keep using'],
|
|
151
175
|
['weave', 'Weave candidates', 'Worth improving next'],
|
|
@@ -206,6 +230,30 @@ COPY.ko = {
|
|
|
206
230
|
runWindow: '기록 기간',
|
|
207
231
|
totalRuns: 'Run 수',
|
|
208
232
|
refCount: '참조 횟수',
|
|
233
|
+
zoomIn: '확대',
|
|
234
|
+
zoomOut: '축소',
|
|
235
|
+
zoomReset: '전체 보기',
|
|
236
|
+
mapGuideEyebrow: '지도 읽는 법',
|
|
237
|
+
mapGuideTitle: '이 지도는 무엇인가요?',
|
|
238
|
+
mapGuideText: '원 하나하나가 Tink가 알고 있는 것입니다: 하네스, 하네스가 쓰는 규칙, 읽는 메모리 파일. 로컬에 보이는 기록만으로 그려집니다.',
|
|
239
|
+
guideItems: [
|
|
240
|
+
['큰 원', '하네스를 많이 쓸수록 원이 커집니다.'],
|
|
241
|
+
['색상', '파란 원이 하네스이고, 회색 계열은 규칙·메모리·단계입니다.'],
|
|
242
|
+
['선', '"함께 일한다"는 뜻입니다. 하네스가 규칙을 쓰거나, 메모리를 읽거나, 다음 단계로 이어질 때 선이 생깁니다.'],
|
|
243
|
+
['작은 점', '하네스 주변의 작은 점들은 사용·근거·점수 신호입니다.']
|
|
244
|
+
],
|
|
245
|
+
controlItems: [
|
|
246
|
+
['휠 / + −', '확대·축소'],
|
|
247
|
+
['드래그', '지도 이동'],
|
|
248
|
+
['더블클릭', '전체 보기로 복귀'],
|
|
249
|
+
['원 클릭', '아래에서 상세 보기']
|
|
250
|
+
],
|
|
251
|
+
relationTitle: '선의 의미',
|
|
252
|
+
relationItems: [
|
|
253
|
+
['uses_rule', '이 하네스가 그 규칙을 불러옵니다'],
|
|
254
|
+
['uses_memory', '이 하네스가 그 메모리 파일을 읽습니다'],
|
|
255
|
+
['sequence', '이 하네스 다음에 그 단계가 자주 이어집니다']
|
|
256
|
+
],
|
|
209
257
|
navLabel: '탐색',
|
|
210
258
|
operator: '작업자',
|
|
211
259
|
online: 'Tink 온라인',
|
|
@@ -214,7 +262,7 @@ COPY.ko = {
|
|
|
214
262
|
heroText: '보이는 Tink run, rule, memory reference, harness 관계를 하나의 로컬 대시보드로 보여줍니다. 이 보고서는 제안만 준비하며 재사용 상태를 직접 수정하지 않습니다.',
|
|
215
263
|
generated: '생성 시각',
|
|
216
264
|
harnessMap: '하네스 지도',
|
|
217
|
-
mapHelp: '보이는 Tink 기록에서 하네스, rule, memory, stage 관계를 그립니다. 노드를 클릭하면 자세히 볼 수 있습니다.',
|
|
265
|
+
mapHelp: '보이는 Tink 기록에서 하네스, rule, memory, stage 관계를 그립니다. 휠로 확대, 드래그로 이동, 노드를 클릭하면 자세히 볼 수 있습니다.',
|
|
218
266
|
graphControls: '그래프 조작',
|
|
219
267
|
full: '전체',
|
|
220
268
|
core: '핵심',
|
|
@@ -570,8 +618,7 @@ function buildGraphLayout(summary) {
|
|
|
570
618
|
glow: score >= 50 || radius >= 20
|
|
571
619
|
};
|
|
572
620
|
});
|
|
573
|
-
const
|
|
574
|
-
const virtualEdges = [];
|
|
621
|
+
const orbitSystems = [];
|
|
575
622
|
for (const node of positioned.filter((item) => item.type === 'harness')) {
|
|
576
623
|
const harnessId = shortLabel(node.id);
|
|
577
624
|
const harness = harnessById.get(harnessId);
|
|
@@ -579,38 +626,25 @@ function buildGraphLayout(summary) {
|
|
|
579
626
|
const uses = Number(harness.signals?.uses || 0);
|
|
580
627
|
const evidenceCount = (harness.evidence_handles || []).length;
|
|
581
628
|
const factorCount = (harness.candidate_score?.factors || []).length;
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
radius: satellite.radius,
|
|
599
|
-
color: TYPE_COLORS[satellite.kind] || TYPE_COLORS.unknown,
|
|
600
|
-
glow: satellite.kind === 'score'
|
|
601
|
-
};
|
|
602
|
-
augmented.push(child);
|
|
603
|
-
virtualEdges.push({
|
|
604
|
-
source: node.id,
|
|
605
|
-
target: child.id,
|
|
606
|
-
type: satellite.kind,
|
|
607
|
-
count: 1
|
|
608
|
-
});
|
|
609
|
-
});
|
|
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
|
+
}
|
|
610
645
|
}
|
|
611
|
-
const byId = new Map(
|
|
612
|
-
const
|
|
613
|
-
const drawnEdges = [...filteredEdges, ...virtualEdges]
|
|
646
|
+
const byId = new Map(positioned.map((node) => [node.id, node]));
|
|
647
|
+
const drawnEdges = getRenderableEdges(edges)
|
|
614
648
|
.map((edge) => ({
|
|
615
649
|
...edge,
|
|
616
650
|
sourceNode: byId.get(edge.source),
|
|
@@ -619,15 +653,26 @@ function buildGraphLayout(summary) {
|
|
|
619
653
|
.filter((edge) => edge.sourceNode && edge.targetNode)
|
|
620
654
|
.slice(0, 240);
|
|
621
655
|
|
|
622
|
-
return { nodes:
|
|
656
|
+
return { nodes: positioned, edges: drawnEdges, orbitSystems };
|
|
623
657
|
}
|
|
624
658
|
|
|
625
659
|
function renderGraphCanvas(summary, copy) {
|
|
626
|
-
const { nodes, edges } = buildGraphLayout(summary);
|
|
660
|
+
const { nodes, edges, orbitSystems } = buildGraphLayout(summary);
|
|
627
661
|
const strongest = nodes
|
|
628
662
|
.filter((node) => node.type === 'harness')
|
|
629
663
|
.sort((a, b) => Number(b.weight || 0) - Number(a.weight || 0))
|
|
630
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
|
+
});
|
|
631
676
|
const mapTitle = copy.knowledgeGraph || copy.harnessMap || 'Harness map';
|
|
632
677
|
const mapEyebrow = copy.harnessMap && copy.harnessMap !== mapTitle ? copy.harnessMap : '';
|
|
633
678
|
return `
|
|
@@ -638,13 +683,40 @@ function renderGraphCanvas(summary, copy) {
|
|
|
638
683
|
<h2 id="map-title">${escapeHtml(mapTitle)}</h2>
|
|
639
684
|
<p>${escapeHtml(copy.mapHelp)}</p>
|
|
640
685
|
</div>
|
|
641
|
-
<div class="map-controls
|
|
642
|
-
<
|
|
643
|
-
|
|
686
|
+
<div class="map-controls-row">
|
|
687
|
+
<div class="map-controls" aria-label="${escapeAttr(copy.graphControls)}">
|
|
688
|
+
<button class="active" type="button" data-mode="full" aria-pressed="true">${escapeHtml(copy.full)}</button>
|
|
689
|
+
<button type="button" data-mode="core" aria-pressed="false">${escapeHtml(copy.core)}</button>
|
|
690
|
+
</div>
|
|
691
|
+
<div class="map-controls" aria-label="zoom">
|
|
692
|
+
<button type="button" data-zoom="in" aria-label="${escapeAttr(copy.zoomIn || 'Zoom in')}" title="${escapeAttr(copy.zoomIn || 'Zoom in')}">+</button>
|
|
693
|
+
<button type="button" data-zoom="out" aria-label="${escapeAttr(copy.zoomOut || 'Zoom out')}" title="${escapeAttr(copy.zoomOut || 'Zoom out')}">−</button>
|
|
694
|
+
<button type="button" data-zoom="reset" title="${escapeAttr(copy.zoomReset || 'Reset view')}">${escapeHtml(copy.zoomReset || 'Reset')}</button>
|
|
695
|
+
</div>
|
|
644
696
|
</div>
|
|
645
697
|
</div>
|
|
646
698
|
<svg class="graph-canvas" viewBox="0 0 1090 680" role="img" aria-label="Harness health graph">
|
|
647
|
-
<
|
|
699
|
+
<defs>
|
|
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"/>
|
|
704
|
+
</radialGradient>
|
|
705
|
+
${Object.entries(TYPE_COLORS).map(([type, color]) => `
|
|
706
|
+
<radialGradient id="node-grad-${escapeAttr(type)}" cx="32%" cy="28%" r="78%">
|
|
707
|
+
<stop offset="0%" style="stop-color: #FFFFFF; stop-opacity: 0.42"/>
|
|
708
|
+
<stop offset="38%" style="stop-color: ${escapeAttr(color)}; stop-opacity: 0.98"/>
|
|
709
|
+
<stop offset="100%" style="stop-color: ${escapeAttr(color)}; stop-opacity: 0.78"/>
|
|
710
|
+
</radialGradient>
|
|
711
|
+
`).join('')}
|
|
712
|
+
</defs>
|
|
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>
|
|
719
|
+
<g id="graph-viewport">
|
|
648
720
|
<g class="edges">
|
|
649
721
|
${edges.map((edge, index) => `
|
|
650
722
|
<line
|
|
@@ -662,6 +734,22 @@ function renderGraphCanvas(summary, copy) {
|
|
|
662
734
|
/>
|
|
663
735
|
`).join('')}
|
|
664
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>
|
|
665
753
|
<g class="nodes">
|
|
666
754
|
${nodes.map((node, index) => {
|
|
667
755
|
const seed = hashString(node.id);
|
|
@@ -671,6 +759,9 @@ function renderGraphCanvas(summary, copy) {
|
|
|
671
759
|
const floatY = (((seed >> 3) % 7) - 3).toFixed(1);
|
|
672
760
|
return `
|
|
673
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
|
+
` : ''}
|
|
674
765
|
<circle
|
|
675
766
|
class="graph-node ${node.type === 'harness' ? 'is-interactive' : ''}"
|
|
676
767
|
style="--enter-delay: ${Math.min(index * 9, 1100)}ms"
|
|
@@ -686,8 +777,8 @@ function renderGraphCanvas(summary, copy) {
|
|
|
686
777
|
cx="${node.x.toFixed(1)}"
|
|
687
778
|
cy="${node.y.toFixed(1)}"
|
|
688
779
|
r="${node.radius.toFixed(1)}"
|
|
689
|
-
fill="
|
|
690
|
-
fill-opacity="${node.type === 'harness' ? '
|
|
780
|
+
fill="url(#node-grad-${escapeAttr(TYPE_COLORS[node.type] ? node.type : 'unknown')})"
|
|
781
|
+
fill-opacity="${node.type === 'harness' ? '1' : '0.85'}"
|
|
691
782
|
stroke="${escapeAttr('var(--text-secondary)')}"
|
|
692
783
|
stroke-opacity="${node.glow ? '0.9' : '0.18'}"
|
|
693
784
|
stroke-width="${node.glow ? '1.8' : '0.8'}"
|
|
@@ -703,6 +794,7 @@ function renderGraphCanvas(summary, copy) {
|
|
|
703
794
|
<text x="${(node.x + node.radius + 7).toFixed(1)}" y="${(node.y + 4).toFixed(1)}">${escapeHtml(node.label)}</text>
|
|
704
795
|
`).join('')}
|
|
705
796
|
</g>
|
|
797
|
+
</g>
|
|
706
798
|
</svg>
|
|
707
799
|
<div class="graph-tooltip" id="graph-tooltip" role="status" aria-live="polite"></div>
|
|
708
800
|
<div class="map-caption">
|
|
@@ -1008,6 +1100,39 @@ function renderGraphOverview(graph = {}, copy) {
|
|
|
1008
1100
|
`;
|
|
1009
1101
|
}
|
|
1010
1102
|
|
|
1103
|
+
function renderMapGuide(copy) {
|
|
1104
|
+
const guideItems = Array.isArray(copy.guideItems) ? copy.guideItems : [];
|
|
1105
|
+
const controlItems = Array.isArray(copy.controlItems) ? copy.controlItems : [];
|
|
1106
|
+
const relationItems = Array.isArray(copy.relationItems) ? copy.relationItems : [];
|
|
1107
|
+
return `
|
|
1108
|
+
<section class="insight-card map-guide">
|
|
1109
|
+
<div class="panel-title">
|
|
1110
|
+
<p class="eyebrow">${escapeHtml(copy.mapGuideEyebrow || 'HOW TO READ')}</p>
|
|
1111
|
+
<h2>${escapeHtml(copy.mapGuideTitle || 'What is this map?')}</h2>
|
|
1112
|
+
<p>${escapeHtml(copy.mapGuideText || '')}</p>
|
|
1113
|
+
</div>
|
|
1114
|
+
<ul class="guide-list">
|
|
1115
|
+
${guideItems.map(([term, text]) => `
|
|
1116
|
+
<li><strong>${escapeHtml(term)}</strong><span>${escapeHtml(text)}</span></li>
|
|
1117
|
+
`).join('')}
|
|
1118
|
+
</ul>
|
|
1119
|
+
<ul class="guide-list guide-controls">
|
|
1120
|
+
${controlItems.map(([key, text]) => `
|
|
1121
|
+
<li><kbd>${escapeHtml(key)}</kbd><span>${escapeHtml(text)}</span></li>
|
|
1122
|
+
`).join('')}
|
|
1123
|
+
</ul>
|
|
1124
|
+
${relationItems.length ? `
|
|
1125
|
+
<p class="detail-label">${escapeHtml(copy.relationTitle || 'What the lines mean')}</p>
|
|
1126
|
+
<ul class="guide-list guide-relations">
|
|
1127
|
+
${relationItems.map(([type, text]) => `
|
|
1128
|
+
<li><code>${escapeHtml(type)}</code><span>${escapeHtml(text)}</span></li>
|
|
1129
|
+
`).join('')}
|
|
1130
|
+
</ul>
|
|
1131
|
+
` : ''}
|
|
1132
|
+
</section>
|
|
1133
|
+
`;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1011
1136
|
function renderRoutingCard(copy) {
|
|
1012
1137
|
const rules = Array.isArray(copy.routeRules) ? copy.routeRules : [];
|
|
1013
1138
|
return `
|
|
@@ -1255,14 +1380,30 @@ function renderScript(harnesses, copy) {
|
|
|
1255
1380
|
const setFilterStatus = (value) => {
|
|
1256
1381
|
if (filterStatus) filterStatus.textContent = value;
|
|
1257
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
|
+
}
|
|
1258
1396
|
function clearSelection() {
|
|
1259
1397
|
nodes.forEach((item) => item.classList.remove('is-selected', 'is-related'));
|
|
1260
1398
|
edges.forEach((item) => item.classList.remove('is-related'));
|
|
1261
1399
|
cards.forEach((item) => item.classList.remove('is-selected'));
|
|
1400
|
+
if (graphCanvas) graphCanvas.classList.remove('has-selection');
|
|
1401
|
+
syncOrbits();
|
|
1262
1402
|
}
|
|
1263
1403
|
function selectNode(node) {
|
|
1264
1404
|
clearSelection();
|
|
1265
1405
|
node.classList.add('is-selected');
|
|
1406
|
+
if (graphCanvas) graphCanvas.classList.add('has-selection');
|
|
1266
1407
|
const id = node.dataset.nodeId;
|
|
1267
1408
|
const item = byHarnessId.get(id);
|
|
1268
1409
|
edges.forEach((edge) => {
|
|
@@ -1290,6 +1431,7 @@ function renderScript(harnesses, copy) {
|
|
|
1290
1431
|
} else if (selectedPanel) {
|
|
1291
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>';
|
|
1292
1433
|
}
|
|
1434
|
+
syncOrbits();
|
|
1293
1435
|
}
|
|
1294
1436
|
function selectHarness(id) {
|
|
1295
1437
|
const node = nodeById('harness:' + id);
|
|
@@ -1307,7 +1449,9 @@ function renderScript(harnesses, copy) {
|
|
|
1307
1449
|
nodes.forEach((node) => node.classList.toggle('is-hidden', mode === 'core' && node.dataset.core !== 'true'));
|
|
1308
1450
|
const visibleIds = new Set(nodes.filter((node) => !node.classList.contains('is-hidden')).map((node) => node.dataset.nodeId));
|
|
1309
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'));
|
|
1310
1453
|
setStatus(mode === 'core' ? copy.coreMode : copy.showingAll);
|
|
1454
|
+
syncOrbits();
|
|
1311
1455
|
}
|
|
1312
1456
|
function filterRecommendation(value, button) {
|
|
1313
1457
|
const alreadyActive = button.classList.contains('active-filter');
|
|
@@ -1321,6 +1465,7 @@ function renderScript(harnesses, copy) {
|
|
|
1321
1465
|
cards.forEach((card) => card.classList.remove('is-filtered-out'));
|
|
1322
1466
|
setStatus(copy.showingAll);
|
|
1323
1467
|
setFilterStatus(copy.showingAll);
|
|
1468
|
+
syncOrbits();
|
|
1324
1469
|
return;
|
|
1325
1470
|
}
|
|
1326
1471
|
button.classList.add('active-filter');
|
|
@@ -1335,6 +1480,7 @@ function renderScript(harnesses, copy) {
|
|
|
1335
1480
|
const label = recLabelByFilter[value] || value;
|
|
1336
1481
|
setStatus(copy.filteredTo + ': ' + label);
|
|
1337
1482
|
setFilterStatus(copy.filteredTo + ': ' + label);
|
|
1483
|
+
syncOrbits();
|
|
1338
1484
|
}
|
|
1339
1485
|
interactiveNodes.forEach((node) => {
|
|
1340
1486
|
node.addEventListener('click', () => selectNode(node));
|
|
@@ -1364,6 +1510,88 @@ function renderScript(harnesses, copy) {
|
|
|
1364
1510
|
document.querySelectorAll('[data-mode]').forEach((button) => {
|
|
1365
1511
|
button.addEventListener('click', () => applyMode(button.dataset.mode));
|
|
1366
1512
|
});
|
|
1513
|
+
const graphSvg = document.querySelector('.graph-canvas');
|
|
1514
|
+
const graphViewport = document.getElementById('graph-viewport');
|
|
1515
|
+
if (graphSvg && graphViewport) {
|
|
1516
|
+
const view = { x: 0, y: 0, k: 1 };
|
|
1517
|
+
graphViewport.style.transformOrigin = '0 0';
|
|
1518
|
+
const applyView = () => {
|
|
1519
|
+
graphViewport.style.transform = 'translate(' + view.x + 'px, ' + view.y + 'px) scale(' + view.k + ')';
|
|
1520
|
+
};
|
|
1521
|
+
const svgPoint = (event) => {
|
|
1522
|
+
const pt = graphSvg.createSVGPoint();
|
|
1523
|
+
pt.x = event.clientX;
|
|
1524
|
+
pt.y = event.clientY;
|
|
1525
|
+
return pt.matrixTransform(graphSvg.getScreenCTM().inverse());
|
|
1526
|
+
};
|
|
1527
|
+
const zoomAt = (factor, cx, cy) => {
|
|
1528
|
+
const k = Math.min(5, Math.max(0.4, view.k * factor));
|
|
1529
|
+
const real = k / view.k;
|
|
1530
|
+
view.x = cx - (cx - view.x) * real;
|
|
1531
|
+
view.y = cy - (cy - view.y) * real;
|
|
1532
|
+
view.k = k;
|
|
1533
|
+
applyView();
|
|
1534
|
+
};
|
|
1535
|
+
const resetView = () => {
|
|
1536
|
+
graphViewport.classList.add('is-resetting');
|
|
1537
|
+
view.x = 0; view.y = 0; view.k = 1;
|
|
1538
|
+
applyView();
|
|
1539
|
+
setTimeout(() => graphViewport.classList.remove('is-resetting'), 360);
|
|
1540
|
+
};
|
|
1541
|
+
graphSvg.addEventListener('wheel', (event) => {
|
|
1542
|
+
event.preventDefault();
|
|
1543
|
+
const point = svgPoint(event);
|
|
1544
|
+
zoomAt(event.deltaY < 0 ? 1.15 : 1 / 1.15, point.x, point.y);
|
|
1545
|
+
}, { passive: false });
|
|
1546
|
+
let panState = null;
|
|
1547
|
+
graphSvg.addEventListener('pointerdown', (event) => {
|
|
1548
|
+
if (event.button !== 0) return;
|
|
1549
|
+
panState = { x: event.clientX, y: event.clientY, moved: false };
|
|
1550
|
+
graphSvg.setPointerCapture(event.pointerId);
|
|
1551
|
+
});
|
|
1552
|
+
graphSvg.addEventListener('pointermove', (event) => {
|
|
1553
|
+
if (!panState) return;
|
|
1554
|
+
const dx = event.clientX - panState.x;
|
|
1555
|
+
const dy = event.clientY - panState.y;
|
|
1556
|
+
if (!panState.moved && Math.abs(dx) + Math.abs(dy) < 3) return;
|
|
1557
|
+
panState.moved = true;
|
|
1558
|
+
graphSvg.classList.add('is-panning');
|
|
1559
|
+
const scale = 1090 / graphSvg.clientWidth;
|
|
1560
|
+
view.x += dx * scale;
|
|
1561
|
+
view.y += dy * scale;
|
|
1562
|
+
panState.x = event.clientX;
|
|
1563
|
+
panState.y = event.clientY;
|
|
1564
|
+
applyView();
|
|
1565
|
+
});
|
|
1566
|
+
let suppressClick = false;
|
|
1567
|
+
const endPan = () => {
|
|
1568
|
+
suppressClick = Boolean(panState && panState.moved);
|
|
1569
|
+
panState = null;
|
|
1570
|
+
graphSvg.classList.remove('is-panning');
|
|
1571
|
+
};
|
|
1572
|
+
graphSvg.addEventListener('pointerup', endPan);
|
|
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
|
+
});
|
|
1584
|
+
graphSvg.addEventListener('dblclick', (event) => {
|
|
1585
|
+
event.preventDefault();
|
|
1586
|
+
resetView();
|
|
1587
|
+
});
|
|
1588
|
+
document.querySelectorAll('[data-zoom]').forEach((button) => {
|
|
1589
|
+
button.addEventListener('click', () => {
|
|
1590
|
+
if (button.dataset.zoom === 'reset') return resetView();
|
|
1591
|
+
zoomAt(button.dataset.zoom === 'in' ? 1.3 : 1 / 1.3, 545, 340);
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1367
1595
|
const VALID_TABS = ['home', 'harnesses', 'memory', 'graph', 'activity'];
|
|
1368
1596
|
const navLinks = Array.from(document.querySelectorAll('.nav a[data-tab]'));
|
|
1369
1597
|
const pages = Array.from(document.querySelectorAll('.page'));
|
|
@@ -2020,6 +2248,147 @@ function renderStyles() {
|
|
|
2020
2248
|
transition: opacity 160ms ease, transform 160ms ease;
|
|
2021
2249
|
}
|
|
2022
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
|
+
|
|
2315
|
+
.map-controls-row {
|
|
2316
|
+
display: flex;
|
|
2317
|
+
gap: var(--space-2);
|
|
2318
|
+
align-items: center;
|
|
2319
|
+
flex-wrap: wrap;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
.graph-canvas {
|
|
2323
|
+
cursor: grab;
|
|
2324
|
+
touch-action: none;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
.graph-canvas.is-panning { cursor: grabbing; }
|
|
2328
|
+
|
|
2329
|
+
#graph-viewport {
|
|
2330
|
+
will-change: transform;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
#graph-viewport.is-resetting {
|
|
2334
|
+
transition: transform 340ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
.graph-node.is-interactive {
|
|
2338
|
+
filter: drop-shadow(0 5px 7px rgba(0, 0, 0, 0.55));
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
.graph-node.is-interactive:hover,
|
|
2342
|
+
.graph-node.is-interactive:focus-visible {
|
|
2343
|
+
filter: drop-shadow(0 9px 14px rgba(0, 0, 0, 0.65));
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
.map-guide .panel-title p:last-child {
|
|
2347
|
+
font-size: 12px;
|
|
2348
|
+
line-height: 1.5;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
.guide-list {
|
|
2352
|
+
margin: var(--space-3) 0 0;
|
|
2353
|
+
padding: 0;
|
|
2354
|
+
list-style: none;
|
|
2355
|
+
display: grid;
|
|
2356
|
+
gap: var(--space-2);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
.guide-list li {
|
|
2360
|
+
display: grid;
|
|
2361
|
+
grid-template-columns: 76px 1fr;
|
|
2362
|
+
gap: var(--space-2);
|
|
2363
|
+
align-items: start;
|
|
2364
|
+
font-size: 12px;
|
|
2365
|
+
line-height: 1.45;
|
|
2366
|
+
color: var(--text-secondary);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
.guide-list strong {
|
|
2370
|
+
color: var(--text-primary);
|
|
2371
|
+
font-size: 12px;
|
|
2372
|
+
font-weight: 500;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.guide-list kbd {
|
|
2376
|
+
font-family: var(--font-mono);
|
|
2377
|
+
font-size: 10px;
|
|
2378
|
+
color: var(--accent-text);
|
|
2379
|
+
border: 1px solid var(--border-default);
|
|
2380
|
+
border-radius: var(--radius-sm);
|
|
2381
|
+
background: var(--bg-hover);
|
|
2382
|
+
padding: 2px 5px;
|
|
2383
|
+
text-align: center;
|
|
2384
|
+
white-space: nowrap;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
.guide-controls { border-top: 1px solid var(--border-default); padding-top: var(--space-3); }
|
|
2388
|
+
|
|
2389
|
+
.guide-relations li { grid-template-columns: 96px 1fr; }
|
|
2390
|
+
.guide-relations code { font-size: 10px; }
|
|
2391
|
+
|
|
2023
2392
|
.map-legend {
|
|
2024
2393
|
display: flex;
|
|
2025
2394
|
gap: var(--space-3);
|
|
@@ -2050,6 +2419,8 @@ function renderStyles() {
|
|
|
2050
2419
|
.graph-node,
|
|
2051
2420
|
.graph-edge,
|
|
2052
2421
|
.graph-canvas text,
|
|
2422
|
+
.orbit-spin,
|
|
2423
|
+
.star-twinkle,
|
|
2053
2424
|
.page.is-active { animation: none; }
|
|
2054
2425
|
.graph-node { transition: none; }
|
|
2055
2426
|
}
|
|
@@ -2850,9 +3221,10 @@ function renderReport(summary) {
|
|
|
2850
3221
|
<aside class="right-rail" aria-label="Insights">
|
|
2851
3222
|
<div data-rail="home harnesses memory activity">${renderStats(summary, copy)}</div>
|
|
2852
3223
|
<div data-rail="home harnesses">${renderConfidence(summary, copy)}</div>
|
|
2853
|
-
<div data-rail="graph">${
|
|
2854
|
-
<div data-rail="home harnesses">${renderImportantHarnesses(harnesses, copy)}</div>
|
|
3224
|
+
<div data-rail="graph">${renderMapGuide(copy)}</div>
|
|
2855
3225
|
<div data-rail="graph">${renderSelectedPanel(harnesses, copy)}</div>
|
|
3226
|
+
<div data-rail="home harnesses">${renderImportantHarnesses(harnesses, copy)}</div>
|
|
3227
|
+
<div data-rail="graph">${renderGraphOverview(summary.graph || {}, copy)}</div>
|
|
2856
3228
|
</aside>
|
|
2857
3229
|
</div>
|
|
2858
3230
|
${renderContractMetadata(copy)}
|