tink-harness 1.9.2 → 1.9.4
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.4] - 2026-06-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Harness cards now sort by usage and open with a smooth expand animation showing richer details: last used, success/failure counts, context cost, co-used harness chips, score factors, safe next action, and evidence handles.
|
|
14
|
+
- Added an evaluation & maintenance history section to the Harnesses tab, fed by `.tink/maintenance/ledger.jsonl` (new `maintenance_events` field in the lifecycle summary).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- The lifecycle generator now strips a UTF-8 BOM before parsing JSONL files, so the first ledger entry is no longer dropped.
|
|
19
|
+
|
|
20
|
+
## [1.9.3] - 2026-06-10
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Graph nodes now drift gently with per-node organic float motion, staggered entrance animation, and a pulse ring on the selected node.
|
|
25
|
+
- Edges fade in progressively and respond to selection with smooth opacity/width transitions.
|
|
26
|
+
- Added a node-type color legend under the graph and polished the tooltip with fade/slide motion.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Graph hover now scales the node smoothly instead of only changing the stroke.
|
|
31
|
+
- The graph tab shows only graph-related information; honors prefers-reduced-motion.
|
|
32
|
+
|
|
9
33
|
## [1.9.2] - 2026-06-10
|
|
10
34
|
|
|
11
35
|
### Added
|
package/VERSIONING.md
CHANGED
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ function readJson(filePath, fallback) {
|
|
|
15
15
|
|
|
16
16
|
function readLines(filePath) {
|
|
17
17
|
if (!fs.existsSync(filePath)) return [];
|
|
18
|
-
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
18
|
+
return fs.readFileSync(filePath, 'utf8').replace(/^/, '').split(/\r?\n/).filter(Boolean);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function listFiles(dirPath, suffix) {
|
|
@@ -517,6 +517,28 @@ function summarize(root) {
|
|
|
517
517
|
item.candidate_score = scoreCandidate(item);
|
|
518
518
|
}
|
|
519
519
|
const harnessSummaries = [...summaries.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
520
|
+
|
|
521
|
+
const ledgerPath = path.join(root, '.tink/maintenance/ledger.jsonl');
|
|
522
|
+
const knownHarnessIds = [...summaries.keys()];
|
|
523
|
+
const maintenanceEvents = parseJsonl(ledgerPath)
|
|
524
|
+
.map((entry) => {
|
|
525
|
+
const refs = [...(Array.isArray(entry.files) ? entry.files : []), ...(Array.isArray(entry.evidence) ? entry.evidence : []), String(entry.op_id || '')];
|
|
526
|
+
const related = knownHarnessIds.filter((id) =>
|
|
527
|
+
refs.some((ref) => String(ref).includes(`${id}.md`) || String(ref).includes(`harness:${id}`) || String(ref).includes(`-${id}-`))
|
|
528
|
+
).sort();
|
|
529
|
+
return {
|
|
530
|
+
timestamp: entry.timestamp || '',
|
|
531
|
+
op_id: entry.op_id || '',
|
|
532
|
+
type: entry.type || 'unknown',
|
|
533
|
+
files: (Array.isArray(entry.files) ? entry.files : []).slice(0, 8),
|
|
534
|
+
result: entry.result || 'unknown',
|
|
535
|
+
approval: entry.approval || '',
|
|
536
|
+
harnesses: related
|
|
537
|
+
};
|
|
538
|
+
})
|
|
539
|
+
.sort((a, b) => String(b.timestamp).localeCompare(String(a.timestamp)))
|
|
540
|
+
.slice(0, 60);
|
|
541
|
+
|
|
520
542
|
return {
|
|
521
543
|
generated_at: new Date().toISOString(),
|
|
522
544
|
run_window: {
|
|
@@ -530,11 +552,13 @@ function summarize(root) {
|
|
|
530
552
|
'.tink/memory/*.md',
|
|
531
553
|
'.tink/runs/*.md',
|
|
532
554
|
'.tink/maintenance/weave-queue.json',
|
|
533
|
-
'.tink/maintenance/friction.jsonl'
|
|
555
|
+
'.tink/maintenance/friction.jsonl',
|
|
556
|
+
'.tink/maintenance/ledger.jsonl'
|
|
534
557
|
],
|
|
535
558
|
harnesses: harnessSummaries,
|
|
536
559
|
graph: buildGraph(harnessSummaries),
|
|
537
|
-
timeline: buildTimeline(runs, root)
|
|
560
|
+
timeline: buildTimeline(runs, root),
|
|
561
|
+
maintenance_events: maintenanceEvents
|
|
538
562
|
};
|
|
539
563
|
}
|
|
540
564
|
|
|
@@ -130,6 +130,19 @@ const COPY = {
|
|
|
130
130
|
viewAll: 'View all',
|
|
131
131
|
confidenceShort: 'Confidence',
|
|
132
132
|
routingHelp: 'When cast routes a task to a visible-thinking overlay harness.',
|
|
133
|
+
lastUsed: 'Last used',
|
|
134
|
+
successes: 'Successes',
|
|
135
|
+
failures: 'Failures',
|
|
136
|
+
contextCost: 'Context cost',
|
|
137
|
+
coUsedWith: 'Often used with',
|
|
138
|
+
safeNextAction: 'Safe next action',
|
|
139
|
+
scoreFactors: 'Score factors',
|
|
140
|
+
viewInGraph: 'View in graph',
|
|
141
|
+
historyEyebrow: 'HISTORY',
|
|
142
|
+
historyTitle: 'Evaluation & maintenance history',
|
|
143
|
+
historyHelp: 'Approved reusable-state changes from the maintenance ledger, newest first.',
|
|
144
|
+
historyEmpty: 'No ledger history yet.',
|
|
145
|
+
sortNote: 'Sorted by usage',
|
|
133
146
|
groups: [
|
|
134
147
|
['keep', 'Healthy harnesses', 'Ready to keep using'],
|
|
135
148
|
['weave', 'Weave candidates', 'Worth improving next'],
|
|
@@ -174,6 +187,19 @@ COPY.ko = {
|
|
|
174
187
|
viewAll: '전체 보기',
|
|
175
188
|
confidenceShort: '신뢰도',
|
|
176
189
|
routingHelp: 'cast가 생각 보조 overlay 하네스로 라우팅하는 기준입니다.',
|
|
190
|
+
lastUsed: '마지막 사용',
|
|
191
|
+
successes: '성공',
|
|
192
|
+
failures: '실패',
|
|
193
|
+
contextCost: '컨텍스트 비용',
|
|
194
|
+
coUsedWith: '함께 쓰인 하네스',
|
|
195
|
+
safeNextAction: '다음 안전 행동',
|
|
196
|
+
scoreFactors: '점수 요인',
|
|
197
|
+
viewInGraph: '그래프에서 보기',
|
|
198
|
+
historyEyebrow: '히스토리',
|
|
199
|
+
historyTitle: '평가·생성 히스토리',
|
|
200
|
+
historyHelp: '유지보수 장부에 기록된 승인 변경 이력을 최신순으로 보여줍니다.',
|
|
201
|
+
historyEmpty: '아직 장부 기록이 없습니다.',
|
|
202
|
+
sortNote: '사용량 순 정렬',
|
|
177
203
|
navLabel: '탐색',
|
|
178
204
|
operator: '작업자',
|
|
179
205
|
online: 'Tink 온라인',
|
|
@@ -614,9 +640,10 @@ function renderGraphCanvas(summary, copy) {
|
|
|
614
640
|
<svg class="graph-canvas" viewBox="0 0 1090 680" role="img" aria-label="Harness health graph">
|
|
615
641
|
<rect width="1090" height="680" fill="var(--bg-card)"/>
|
|
616
642
|
<g class="edges">
|
|
617
|
-
${edges.map((edge) => `
|
|
643
|
+
${edges.map((edge, index) => `
|
|
618
644
|
<line
|
|
619
645
|
class="graph-edge"
|
|
646
|
+
style="--edge-delay: ${Math.min(index * 5, 850)}ms"
|
|
620
647
|
data-source="${escapeAttr(edge.source)}"
|
|
621
648
|
data-target="${escapeAttr(edge.target)}"
|
|
622
649
|
x1="${edge.sourceNode.x.toFixed(1)}"
|
|
@@ -630,9 +657,17 @@ function renderGraphCanvas(summary, copy) {
|
|
|
630
657
|
`).join('')}
|
|
631
658
|
</g>
|
|
632
659
|
<g class="nodes">
|
|
633
|
-
${nodes.map((node) =>
|
|
660
|
+
${nodes.map((node, index) => {
|
|
661
|
+
const seed = hashString(node.id);
|
|
662
|
+
const floatDuration = (5 + (seed % 50) / 10).toFixed(1);
|
|
663
|
+
const floatDelay = -(seed % 4000);
|
|
664
|
+
const floatX = ((seed % 7) - 3).toFixed(1);
|
|
665
|
+
const floatY = (((seed >> 3) % 7) - 3).toFixed(1);
|
|
666
|
+
return `
|
|
667
|
+
<g class="node-float" style="--float-dur: ${floatDuration}s; --float-delay: ${floatDelay}ms; --float-x: ${floatX}px; --float-y: ${floatY}px">
|
|
634
668
|
<circle
|
|
635
669
|
class="graph-node ${node.type === 'harness' ? 'is-interactive' : ''}"
|
|
670
|
+
style="--enter-delay: ${Math.min(index * 9, 1100)}ms"
|
|
636
671
|
tabindex="${node.type === 'harness' ? '0' : '-1'}"
|
|
637
672
|
role="${node.type === 'harness' ? 'button' : 'presentation'}"
|
|
638
673
|
aria-label="${escapeAttr(`${copy.tooltipPrefix}: ${node.label}`)}"
|
|
@@ -653,7 +688,9 @@ function renderGraphCanvas(summary, copy) {
|
|
|
653
688
|
>
|
|
654
689
|
<title>${escapeHtml(node.id)}</title>
|
|
655
690
|
</circle>
|
|
656
|
-
|
|
691
|
+
</g>
|
|
692
|
+
`;
|
|
693
|
+
}).join('')}
|
|
657
694
|
</g>
|
|
658
695
|
<g class="labels">
|
|
659
696
|
${strongest.map((node) => `
|
|
@@ -665,9 +702,13 @@ function renderGraphCanvas(summary, copy) {
|
|
|
665
702
|
<div class="map-caption">
|
|
666
703
|
<span id="graph-status">${escapeHtml(copy.showingAll)}</span>
|
|
667
704
|
<span>${escapeHtml(copy.nodeSize)}</span>
|
|
668
|
-
<span>${escapeHtml(copy.colorType)}</span>
|
|
669
705
|
<span>${escapeHtml(copy.linesRelations)}</span>
|
|
670
706
|
</div>
|
|
707
|
+
<div class="map-legend" aria-label="${escapeAttr(copy.nodeTypes || 'Node types')}">
|
|
708
|
+
${['harness', 'rule', 'memory', 'stage', 'signal', 'evidence', 'score'].map((type) => `
|
|
709
|
+
<span class="legend-chip"><i style="background: ${escapeAttr(TYPE_COLORS[type] || TYPE_COLORS.unknown)}"></i>${escapeHtml(type)}</span>
|
|
710
|
+
`).join('')}
|
|
711
|
+
</div>
|
|
671
712
|
</section>
|
|
672
713
|
`;
|
|
673
714
|
}
|
|
@@ -845,26 +886,81 @@ function renderSelectedPanel(harnesses, copy) {
|
|
|
845
886
|
function renderHarness(item, copy) {
|
|
846
887
|
const signals = item.signals || {};
|
|
847
888
|
const score = Number(item.candidate_score?.total || 0);
|
|
889
|
+
const factors = (item.candidate_score?.factors || []).slice(0, 5);
|
|
890
|
+
const coUsed = (signals.co_used_with || []).slice(0, 5);
|
|
891
|
+
const reason = normalizeReason(item.reason, copy);
|
|
848
892
|
return `
|
|
849
|
-
<article class="harness-card ${recommendationClass(item.recommendation)}" data-harness-id="${escapeAttr(item.id)}" data-recommendation="${escapeAttr(item.recommendation || 'unknown')}"
|
|
850
|
-
<
|
|
851
|
-
<
|
|
852
|
-
|
|
893
|
+
<article class="harness-card ${recommendationClass(item.recommendation)}" data-harness-id="${escapeAttr(item.id)}" data-recommendation="${escapeAttr(item.recommendation || 'unknown')}">
|
|
894
|
+
<button class="harness-summary" type="button" aria-expanded="false">
|
|
895
|
+
<div>
|
|
896
|
+
<p class="eyebrow">${escapeHtml(renderCopyValue(item.recommendation, copy))}</p>
|
|
897
|
+
<h3>${escapeHtml(item.id)}</h3>
|
|
898
|
+
</div>
|
|
899
|
+
<div class="harness-mini">
|
|
900
|
+
<span>${escapeHtml(copy.uses)} ${escapeHtml(signals.uses ?? 0)}</span>
|
|
901
|
+
<strong>${escapeHtml(score)}</strong>
|
|
902
|
+
</div>
|
|
903
|
+
<span class="chevron" aria-hidden="true"></span>
|
|
904
|
+
</button>
|
|
905
|
+
<div class="harness-detail">
|
|
906
|
+
<div class="harness-detail-inner">
|
|
907
|
+
${reason ? `<p class="harness-reason">${escapeHtml(reason)}</p>` : ''}
|
|
908
|
+
<dl>
|
|
909
|
+
<div><dt>${escapeHtml(copy.lifecycleState)}</dt><dd>${escapeHtml(renderCopyValue(item.lifecycle_state, copy))}</dd></div>
|
|
910
|
+
<div><dt>${escapeHtml(copy.lastUsed || 'Last used')}</dt><dd>${escapeHtml(signals.last_used ? shortDate(signals.last_used) : renderCopyValue('', copy))}</dd></div>
|
|
911
|
+
<div><dt>${escapeHtml(copy.successes || 'Successes')}</dt><dd>${escapeHtml(signals.successes ?? 0)}</dd></div>
|
|
912
|
+
<div><dt>${escapeHtml(copy.failures || 'Failures')}</dt><dd>${escapeHtml(signals.failures ?? 0)}</dd></div>
|
|
913
|
+
<div><dt>${escapeHtml(copy.blocked)}</dt><dd>${escapeHtml(signals.blocked ?? 0)}</dd></div>
|
|
914
|
+
<div><dt>${escapeHtml(copy.contextCost || 'Context cost')}</dt><dd>${escapeHtml(renderCopyValue(signals.context_cost, copy))}</dd></div>
|
|
915
|
+
</dl>
|
|
916
|
+
${coUsed.length ? `
|
|
917
|
+
<p class="detail-label">${escapeHtml(copy.coUsedWith || 'Often used with')}</p>
|
|
918
|
+
<div class="co-used-chips">${coUsed.map((related) => `<span class="co-chip">${escapeHtml(related.id)} ×${escapeHtml(related.count)}</span>`).join('')}</div>
|
|
919
|
+
` : ''}
|
|
920
|
+
${factors.length ? `
|
|
921
|
+
<p class="detail-label">${escapeHtml(copy.scoreFactors || 'Score factors')}</p>
|
|
922
|
+
<ul class="factor-list">${factors.map((factor) => `<li><span>${escapeHtml(factor.name)}</span><strong>${escapeHtml(factor.points ?? factor.value ?? '')}</strong></li>`).join('')}</ul>
|
|
923
|
+
` : ''}
|
|
924
|
+
${item.safe_next_action ? `
|
|
925
|
+
<p class="detail-label">${escapeHtml(copy.safeNextAction || 'Safe next action')}</p>
|
|
926
|
+
<p class="harness-next">${escapeHtml(item.safe_next_action)}</p>
|
|
927
|
+
` : ''}
|
|
928
|
+
<p class="detail-label">${escapeHtml(copy.evidenceHandles)} (${escapeHtml(String((item.evidence_handles || []).length))})</p>
|
|
929
|
+
<ul class="evidence-list">${renderEvidence(item.evidence_handles, copy)}</ul>
|
|
930
|
+
<button class="link-button" type="button" data-select-harness="${escapeAttr(item.id)}">${escapeHtml(copy.viewInGraph || 'View in graph')} →</button>
|
|
931
|
+
</div>
|
|
853
932
|
</div>
|
|
854
|
-
<strong>${escapeHtml(score)}</strong>
|
|
855
|
-
<dl>
|
|
856
|
-
<div><dt>${escapeHtml(copy.lifecycleState)}</dt><dd>${escapeHtml(renderCopyValue(item.lifecycle_state, copy))}</dd></div>
|
|
857
|
-
<div><dt>${escapeHtml(copy.uses)}</dt><dd>${escapeHtml(signals.uses ?? 0)}</dd></div>
|
|
858
|
-
<div><dt>${escapeHtml(copy.blocked)}</dt><dd>${escapeHtml(signals.blocked ?? 0)}</dd></div>
|
|
859
|
-
</dl>
|
|
860
|
-
<details>
|
|
861
|
-
<summary>${escapeHtml(copy.evidenceHandles)} (${escapeHtml(String((item.evidence_handles || []).length))})</summary>
|
|
862
|
-
<ul>${renderEvidence(item.evidence_handles, copy)}</ul>
|
|
863
|
-
</details>
|
|
864
933
|
</article>
|
|
865
934
|
`;
|
|
866
935
|
}
|
|
867
936
|
|
|
937
|
+
function renderHistorySection(events = [], copy) {
|
|
938
|
+
const items = Array.isArray(events) ? events.slice(0, 30) : [];
|
|
939
|
+
return `
|
|
940
|
+
<section class="history-section">
|
|
941
|
+
<div class="panel-title">
|
|
942
|
+
<p class="eyebrow">${escapeHtml(copy.historyEyebrow || 'HISTORY')}</p>
|
|
943
|
+
<h2>${escapeHtml(copy.historyTitle || 'Evaluation & maintenance history')}</h2>
|
|
944
|
+
<p>${escapeHtml(copy.historyHelp || '')}</p>
|
|
945
|
+
</div>
|
|
946
|
+
${items.length ? `
|
|
947
|
+
<ol class="history-feed">
|
|
948
|
+
${items.map((event) => `
|
|
949
|
+
<li>
|
|
950
|
+
<span class="history-type ${escapeAttr(String(event.type || 'unknown').replace(/[^a-z0-9_-]/gi, '-'))}">${escapeHtml(event.type || 'unknown')}</span>
|
|
951
|
+
<div>
|
|
952
|
+
<strong>${escapeHtml(shortDate(event.timestamp))} · ${escapeHtml(event.result || '')}</strong>
|
|
953
|
+
${event.harnesses?.length ? `<p class="history-harnesses">${event.harnesses.map((id) => escapeHtml(id)).join(', ')}</p>` : ''}
|
|
954
|
+
${event.files?.length ? `<p class="history-files">${event.files.slice(0, 4).map((file) => `<code>${escapeHtml(normalizePath(file).replace(/^.*[\\/]/, ''))}</code>`).join(' ')}</p>` : ''}
|
|
955
|
+
</div>
|
|
956
|
+
</li>
|
|
957
|
+
`).join('')}
|
|
958
|
+
</ol>
|
|
959
|
+
` : `<p class="empty-note">${escapeHtml(copy.historyEmpty || 'No ledger history yet.')}</p>`}
|
|
960
|
+
</section>
|
|
961
|
+
`;
|
|
962
|
+
}
|
|
963
|
+
|
|
868
964
|
function renderGraphOverview(graph = {}, copy) {
|
|
869
965
|
const stats = graphStats(graph);
|
|
870
966
|
const nodeCounts = new Map(stats.nodeCounts);
|
|
@@ -1252,13 +1348,11 @@ function renderScript(harnesses, copy) {
|
|
|
1252
1348
|
selectHarness(button.dataset.selectHarness);
|
|
1253
1349
|
});
|
|
1254
1350
|
});
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
selectHarness(card.dataset.harnessId);
|
|
1261
|
-
}
|
|
1351
|
+
document.querySelectorAll('.harness-summary').forEach((button) => {
|
|
1352
|
+
button.addEventListener('click', () => {
|
|
1353
|
+
const card = button.closest('.harness-card');
|
|
1354
|
+
const expanded = card.classList.toggle('is-expanded');
|
|
1355
|
+
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
1262
1356
|
});
|
|
1263
1357
|
});
|
|
1264
1358
|
document.querySelectorAll('[data-filter-rec]').forEach((button) => {
|
|
@@ -1764,23 +1858,55 @@ function renderStyles() {
|
|
|
1764
1858
|
|
|
1765
1859
|
.graph-canvas text {
|
|
1766
1860
|
pointer-events: none;
|
|
1861
|
+
fill: var(--text-secondary);
|
|
1862
|
+
font-size: 11px;
|
|
1863
|
+
font-family: var(--font-mono);
|
|
1864
|
+
animation: label-in 600ms ease 900ms backwards;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
@keyframes label-in {
|
|
1868
|
+
from { opacity: 0; }
|
|
1869
|
+
to { opacity: 1; }
|
|
1767
1870
|
}
|
|
1768
1871
|
|
|
1769
1872
|
.graph-edge {
|
|
1770
1873
|
stroke: var(--chart-line);
|
|
1771
1874
|
stroke-width: var(--chart-line-w);
|
|
1772
1875
|
opacity: 0.24;
|
|
1876
|
+
transition: opacity 220ms ease, stroke-width 220ms ease;
|
|
1877
|
+
animation: edge-in 700ms ease var(--edge-delay, 0ms) backwards;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
@keyframes edge-in {
|
|
1881
|
+
from { opacity: 0; }
|
|
1773
1882
|
}
|
|
1774
1883
|
|
|
1775
1884
|
.graph-edge.is-related { opacity: 0.9; stroke-width: 2px; }
|
|
1776
1885
|
|
|
1886
|
+
.node-float {
|
|
1887
|
+
animation: node-float var(--float-dur, 6s) ease-in-out var(--float-delay, 0ms) infinite alternate;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
@keyframes node-float {
|
|
1891
|
+
from { transform: translate(0, 0); }
|
|
1892
|
+
to { transform: translate(var(--float-x, 2px), var(--float-y, -2px)); }
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1777
1895
|
.graph-node {
|
|
1778
1896
|
cursor: default;
|
|
1779
1897
|
outline: none;
|
|
1780
|
-
transition: none;
|
|
1781
1898
|
vector-effect: non-scaling-stroke;
|
|
1782
1899
|
shape-rendering: geometricPrecision;
|
|
1783
1900
|
paint-order: stroke fill;
|
|
1901
|
+
transform-box: fill-box;
|
|
1902
|
+
transform-origin: center;
|
|
1903
|
+
transition: opacity 220ms ease, transform 220ms cubic-bezier(0.2, 0.8, 0.3, 1.1), stroke 160ms ease, stroke-opacity 160ms ease, fill-opacity 160ms ease;
|
|
1904
|
+
animation: node-enter 500ms cubic-bezier(0.2, 0.8, 0.3, 1.2) var(--enter-delay, 0ms) backwards;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
@keyframes node-enter {
|
|
1908
|
+
from { opacity: 0; transform: scale(0.2); }
|
|
1909
|
+
to { opacity: 1; transform: scale(1); }
|
|
1784
1910
|
}
|
|
1785
1911
|
|
|
1786
1912
|
.graph-node.is-interactive {
|
|
@@ -1793,33 +1919,87 @@ function renderStyles() {
|
|
|
1793
1919
|
}
|
|
1794
1920
|
|
|
1795
1921
|
.graph-node.is-interactive:hover,
|
|
1796
|
-
.graph-node.is-interactive:focus-visible
|
|
1922
|
+
.graph-node.is-interactive:focus-visible {
|
|
1923
|
+
stroke: var(--accent);
|
|
1924
|
+
stroke-opacity: 1;
|
|
1925
|
+
transform: scale(1.28);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1797
1928
|
.graph-node.is-selected {
|
|
1798
1929
|
stroke: var(--accent);
|
|
1799
1930
|
stroke-opacity: 1;
|
|
1931
|
+
animation: node-pulse 1.6s ease-in-out infinite;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
@keyframes node-pulse {
|
|
1935
|
+
0%, 100% { stroke-width: 1.8px; transform: scale(1.12); }
|
|
1936
|
+
50% { stroke-width: 5px; transform: scale(1.2); }
|
|
1800
1937
|
}
|
|
1801
1938
|
|
|
1802
1939
|
.graph-node.is-related { opacity: 1; }
|
|
1803
1940
|
.graph-node.is-hidden,
|
|
1804
1941
|
.graph-edge.is-hidden,
|
|
1805
1942
|
.graph-node.is-filtered-out,
|
|
1806
|
-
.graph-edge.is-filtered-out { opacity: 0.
|
|
1943
|
+
.graph-edge.is-filtered-out { opacity: 0.12; pointer-events: none; }
|
|
1807
1944
|
|
|
1808
1945
|
.graph-tooltip {
|
|
1809
1946
|
position: fixed;
|
|
1810
1947
|
z-index: 20;
|
|
1811
|
-
|
|
1948
|
+
visibility: hidden;
|
|
1949
|
+
opacity: 0;
|
|
1950
|
+
transform: translateY(4px);
|
|
1951
|
+
transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 160ms;
|
|
1812
1952
|
max-width: 230px;
|
|
1813
1953
|
padding: 8px 10px;
|
|
1814
|
-
border: 1px solid var(--border-
|
|
1954
|
+
border: 1px solid var(--border-hover);
|
|
1815
1955
|
border-radius: var(--radius-md);
|
|
1816
|
-
background: var(--bg-
|
|
1956
|
+
background: var(--bg-selected);
|
|
1817
1957
|
color: var(--text-primary);
|
|
1818
1958
|
font-size: 11px;
|
|
1819
1959
|
line-height: 1.3;
|
|
1820
1960
|
pointer-events: none;
|
|
1961
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
|
1962
|
+
}
|
|
1963
|
+
.graph-tooltip.is-visible {
|
|
1964
|
+
visibility: visible;
|
|
1965
|
+
opacity: 1;
|
|
1966
|
+
transform: translateY(0);
|
|
1967
|
+
transition: opacity 160ms ease, transform 160ms ease;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
.map-legend {
|
|
1971
|
+
display: flex;
|
|
1972
|
+
gap: var(--space-3);
|
|
1973
|
+
margin-top: var(--space-3);
|
|
1974
|
+
padding-top: var(--space-3);
|
|
1975
|
+
border-top: 1px solid var(--border-default);
|
|
1976
|
+
flex-wrap: wrap;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
.legend-chip {
|
|
1980
|
+
display: inline-flex;
|
|
1981
|
+
align-items: center;
|
|
1982
|
+
gap: 6px;
|
|
1983
|
+
color: var(--text-secondary);
|
|
1984
|
+
font-size: 11px;
|
|
1985
|
+
font-family: var(--font-mono);
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
.legend-chip i {
|
|
1989
|
+
width: 9px;
|
|
1990
|
+
height: 9px;
|
|
1991
|
+
border-radius: 50%;
|
|
1992
|
+
display: inline-block;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1996
|
+
.node-float,
|
|
1997
|
+
.graph-node,
|
|
1998
|
+
.graph-edge,
|
|
1999
|
+
.graph-canvas text,
|
|
2000
|
+
.page.is-active { animation: none; }
|
|
2001
|
+
.graph-node { transition: none; }
|
|
1821
2002
|
}
|
|
1822
|
-
.graph-tooltip.is-visible { display: block; }
|
|
1823
2003
|
|
|
1824
2004
|
.map-caption {
|
|
1825
2005
|
display: flex;
|
|
@@ -1898,24 +2078,26 @@ function renderStyles() {
|
|
|
1898
2078
|
|
|
1899
2079
|
.harness-grid {
|
|
1900
2080
|
display: grid;
|
|
1901
|
-
grid-template-columns: repeat(
|
|
1902
|
-
gap: var(--space-
|
|
2081
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2082
|
+
gap: var(--space-2);
|
|
2083
|
+
align-items: start;
|
|
1903
2084
|
}
|
|
1904
2085
|
|
|
1905
2086
|
.harness-card {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
gap: var(--space-2);
|
|
2087
|
+
transition: opacity 160ms ease, border-color 160ms ease;
|
|
2088
|
+
padding: 0;
|
|
2089
|
+
overflow: hidden;
|
|
1910
2090
|
}
|
|
1911
2091
|
|
|
1912
2092
|
.harness-card:hover,
|
|
1913
|
-
.harness-card
|
|
1914
|
-
.harness-card.is-
|
|
2093
|
+
.harness-card.is-selected,
|
|
2094
|
+
.harness-card.is-expanded {
|
|
1915
2095
|
border-color: var(--border-hover);
|
|
1916
2096
|
outline: none;
|
|
1917
2097
|
}
|
|
1918
2098
|
|
|
2099
|
+
.harness-card.is-expanded { border-color: var(--border-strong); }
|
|
2100
|
+
|
|
1919
2101
|
.harness-card.is-filtered-out { display: none; }
|
|
1920
2102
|
|
|
1921
2103
|
.harness-card.keep { border-top: 2px solid var(--success); }
|
|
@@ -1924,32 +2106,206 @@ function renderStyles() {
|
|
|
1924
2106
|
.harness-card.merge_candidate { border-top: 2px solid var(--accent); }
|
|
1925
2107
|
.harness-card.observe { border-top: 2px solid var(--text-secondary); }
|
|
1926
2108
|
|
|
1927
|
-
.harness-
|
|
2109
|
+
.harness-summary {
|
|
2110
|
+
width: 100%;
|
|
2111
|
+
display: grid;
|
|
2112
|
+
grid-template-columns: 1fr auto 14px;
|
|
2113
|
+
align-items: center;
|
|
2114
|
+
gap: var(--space-2);
|
|
2115
|
+
padding: var(--space-3);
|
|
2116
|
+
border: 0;
|
|
2117
|
+
background: transparent;
|
|
2118
|
+
color: var(--text-primary);
|
|
2119
|
+
font-family: var(--font-ui);
|
|
2120
|
+
text-align: left;
|
|
2121
|
+
cursor: pointer;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
.harness-summary:hover { background: var(--bg-hover); }
|
|
1928
2125
|
|
|
1929
|
-
.harness-
|
|
2126
|
+
.harness-summary .eyebrow { margin: 0 0 2px; font-size: 10px; }
|
|
2127
|
+
|
|
2128
|
+
.harness-summary h3 {
|
|
1930
2129
|
margin: 0;
|
|
1931
|
-
font-size:
|
|
2130
|
+
font-size: 14px;
|
|
1932
2131
|
line-height: 1.25;
|
|
1933
2132
|
font-weight: 600;
|
|
2133
|
+
overflow-wrap: anywhere;
|
|
1934
2134
|
}
|
|
1935
2135
|
|
|
1936
|
-
.harness-
|
|
1937
|
-
|
|
1938
|
-
|
|
2136
|
+
.harness-mini {
|
|
2137
|
+
display: grid;
|
|
2138
|
+
gap: 2px;
|
|
2139
|
+
justify-items: end;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
.harness-mini span {
|
|
2143
|
+
color: var(--text-secondary);
|
|
2144
|
+
font-size: 11px;
|
|
2145
|
+
white-space: nowrap;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
.harness-mini strong {
|
|
2149
|
+
font-size: 18px;
|
|
1939
2150
|
line-height: 1;
|
|
1940
2151
|
font-family: var(--font-mono);
|
|
1941
2152
|
font-weight: 600;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
.chevron {
|
|
2156
|
+
width: 7px;
|
|
2157
|
+
height: 7px;
|
|
2158
|
+
border-right: 1.5px solid var(--text-secondary);
|
|
2159
|
+
border-bottom: 1.5px solid var(--text-secondary);
|
|
2160
|
+
transform: rotate(45deg);
|
|
2161
|
+
transition: transform 240ms ease;
|
|
2162
|
+
justify-self: center;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.harness-card.is-expanded .chevron { transform: rotate(225deg); }
|
|
2166
|
+
|
|
2167
|
+
.harness-detail {
|
|
2168
|
+
display: grid;
|
|
2169
|
+
grid-template-rows: 0fr;
|
|
2170
|
+
transition: grid-template-rows 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
.harness-card.is-expanded .harness-detail { grid-template-rows: 1fr; }
|
|
2174
|
+
|
|
2175
|
+
.harness-detail-inner {
|
|
2176
|
+
overflow: hidden;
|
|
2177
|
+
min-height: 0;
|
|
2178
|
+
padding: 0 var(--space-3);
|
|
2179
|
+
display: grid;
|
|
2180
|
+
gap: var(--space-2);
|
|
2181
|
+
opacity: 0;
|
|
2182
|
+
transition: opacity 240ms ease 60ms, padding 320ms ease;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
.harness-card.is-expanded .harness-detail-inner {
|
|
2186
|
+
opacity: 1;
|
|
2187
|
+
padding: var(--space-1) var(--space-3) var(--space-3);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
.harness-reason {
|
|
2191
|
+
margin: 0;
|
|
2192
|
+
color: var(--text-secondary);
|
|
2193
|
+
font-size: 12px;
|
|
2194
|
+
line-height: 1.5;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
.detail-label {
|
|
2198
|
+
margin: var(--space-1) 0 0;
|
|
2199
|
+
color: var(--text-secondary);
|
|
2200
|
+
font-size: 10px;
|
|
2201
|
+
text-transform: uppercase;
|
|
2202
|
+
letter-spacing: 0.06em;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
.co-used-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
2206
|
+
|
|
2207
|
+
.co-chip {
|
|
2208
|
+
border: 1px solid var(--border-default);
|
|
2209
|
+
background: var(--bg-hover);
|
|
2210
|
+
border-radius: var(--radius-sm);
|
|
2211
|
+
padding: 2px 6px;
|
|
2212
|
+
font-size: 11px;
|
|
2213
|
+
font-family: var(--font-mono);
|
|
2214
|
+
color: var(--text-secondary);
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
.factor-list {
|
|
2218
|
+
margin: 0;
|
|
2219
|
+
padding: 0;
|
|
2220
|
+
list-style: none;
|
|
2221
|
+
display: grid;
|
|
2222
|
+
gap: 4px;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
.factor-list li {
|
|
2226
|
+
display: flex;
|
|
2227
|
+
justify-content: space-between;
|
|
2228
|
+
gap: var(--space-2);
|
|
2229
|
+
font-size: 12px;
|
|
2230
|
+
color: var(--text-secondary);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
.factor-list strong { font-family: var(--font-mono); color: var(--text-primary); font-weight: 500; }
|
|
2234
|
+
|
|
2235
|
+
.harness-next {
|
|
2236
|
+
margin: 0;
|
|
1942
2237
|
color: var(--text-primary);
|
|
1943
|
-
|
|
2238
|
+
font-size: 12px;
|
|
2239
|
+
line-height: 1.5;
|
|
1944
2240
|
}
|
|
1945
2241
|
|
|
1946
|
-
.
|
|
2242
|
+
.evidence-list {
|
|
1947
2243
|
margin: 0;
|
|
2244
|
+
padding-left: 16px;
|
|
1948
2245
|
color: var(--text-secondary);
|
|
1949
|
-
font-size:
|
|
1950
|
-
|
|
2246
|
+
font-size: 11px;
|
|
2247
|
+
display: grid;
|
|
2248
|
+
gap: 3px;
|
|
1951
2249
|
}
|
|
1952
2250
|
|
|
2251
|
+
.harness-card .link-button { margin-top: var(--space-1); justify-self: start; }
|
|
2252
|
+
|
|
2253
|
+
.history-section {
|
|
2254
|
+
margin-top: var(--space-6);
|
|
2255
|
+
border-top: 1px solid var(--border-default);
|
|
2256
|
+
padding-top: var(--space-4);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
.history-feed {
|
|
2260
|
+
margin: var(--space-3) 0 0;
|
|
2261
|
+
padding: 0;
|
|
2262
|
+
list-style: none;
|
|
2263
|
+
display: grid;
|
|
2264
|
+
gap: var(--space-2);
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
.history-feed li {
|
|
2268
|
+
display: grid;
|
|
2269
|
+
grid-template-columns: 110px 1fr;
|
|
2270
|
+
gap: var(--space-3);
|
|
2271
|
+
align-items: start;
|
|
2272
|
+
border: 1px solid var(--border-default);
|
|
2273
|
+
border-radius: var(--radius-md);
|
|
2274
|
+
background: var(--bg-card);
|
|
2275
|
+
padding: var(--space-2) var(--space-3);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
.history-type {
|
|
2279
|
+
display: inline-block;
|
|
2280
|
+
font-family: var(--font-mono);
|
|
2281
|
+
font-size: 10px;
|
|
2282
|
+
text-transform: uppercase;
|
|
2283
|
+
letter-spacing: 0.05em;
|
|
2284
|
+
color: var(--text-secondary);
|
|
2285
|
+
border: 1px solid var(--border-default);
|
|
2286
|
+
border-radius: var(--radius-sm);
|
|
2287
|
+
padding: 3px 6px;
|
|
2288
|
+
margin-top: 2px;
|
|
2289
|
+
text-align: center;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.history-type.memory { color: var(--accent-text); border-color: var(--accent-dim); }
|
|
2293
|
+
.history-type.weave { color: var(--warning); border-color: var(--warning-dim); }
|
|
2294
|
+
.history-type.frog { color: var(--danger); border-color: var(--danger-dim); }
|
|
2295
|
+
.history-type.harness-create,
|
|
2296
|
+
.history-type.harness-edit { color: var(--success); border-color: var(--success-dim); }
|
|
2297
|
+
|
|
2298
|
+
.history-feed strong { font-size: 12px; font-weight: 500; }
|
|
2299
|
+
|
|
2300
|
+
.history-harnesses {
|
|
2301
|
+
margin: 2px 0 0;
|
|
2302
|
+
color: var(--text-secondary);
|
|
2303
|
+
font-size: 11px;
|
|
2304
|
+
font-family: var(--font-mono);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
.history-files { margin: 4px 0 0; display: flex; flex-wrap: wrap; gap: 4px; }
|
|
2308
|
+
|
|
1953
2309
|
.harness-card dl,
|
|
1954
2310
|
.selected dl,
|
|
1955
2311
|
.graph-overview dl {
|
|
@@ -2258,7 +2614,7 @@ function renderStyles() {
|
|
|
2258
2614
|
}
|
|
2259
2615
|
.hero-sidebar { width: 100%; }
|
|
2260
2616
|
.project-strip-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2261
|
-
.harness-grid,
|
|
2617
|
+
.harness-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2262
2618
|
.stats-grid { grid-template-columns: 1fr; }
|
|
2263
2619
|
.home-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
2264
2620
|
.home-columns { grid-template-columns: 1fr; }
|
|
@@ -2338,13 +2694,19 @@ function renderReport(summary) {
|
|
|
2338
2694
|
<section class="page-head">
|
|
2339
2695
|
<p class="eyebrow">${escapeHtml(copy.harnessesEyebrow || 'HARNESSES')}</p>
|
|
2340
2696
|
<h1>${escapeHtml(copy.harnessCards)}</h1>
|
|
2341
|
-
<p>${escapeHtml(copy.harnessesHelp || '')}</p>
|
|
2697
|
+
<p>${escapeHtml(copy.harnessesHelp || '')} · ${escapeHtml(copy.sortNote || 'Sorted by usage')}</p>
|
|
2342
2698
|
</section>
|
|
2343
2699
|
<section class="harness-section">
|
|
2344
2700
|
<div class="harness-grid">
|
|
2345
|
-
${harnesses
|
|
2701
|
+
${[...harnesses]
|
|
2702
|
+
.sort((a, b) =>
|
|
2703
|
+
Number(b.signals?.uses || 0) - Number(a.signals?.uses || 0) ||
|
|
2704
|
+
Number(b.candidate_score?.total || 0) - Number(a.candidate_score?.total || 0) ||
|
|
2705
|
+
a.id.localeCompare(b.id))
|
|
2706
|
+
.map((item) => renderHarness(item, copy)).join('\n')}
|
|
2346
2707
|
</div>
|
|
2347
2708
|
</section>
|
|
2709
|
+
${renderHistorySection(summary.maintenance_events, copy)}
|
|
2348
2710
|
</section>
|
|
2349
2711
|
<section class="page" data-page="memory">
|
|
2350
2712
|
${renderMemoryPage(summary, copy)}
|