Initial commit: knox knowledge index

This commit is contained in:
2026-08-29 02:52:32 -07:00
commit 8eecde18b4
31 changed files with 6855 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
{{define "head"}}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Knox{{if .title}} · {{.title}}{{end}}</title>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>body{font-family:system-ui,sans-serif}</style>
</head>
<body class="bg-gray-950 text-gray-100 min-h-screen">
<header class="border-b border-gray-800 p-4 flex items-center justify-between">
<h1 class="text-xl font-bold text-emerald-400"><a href="/">Knox</a>{{if .title}} <span class="text-gray-400 text-sm font-normal">/ {{.title}}</span>{{end}}</h1>
<nav class="flex gap-4 text-sm">
<a href="/" class="text-gray-300 hover:text-white">Dashboard</a>
<a href="/threads" class="text-gray-300 hover:text-white">Threads</a>
</nav>
</header>
{{end}}
{{define "foot"}}</body>
</html>
{{end}}
{{define "not_found"}}
{{template "head" .}}
<main class="max-w-4xl mx-auto p-6 text-center">
<div class="text-gray-500 text-lg">Page not found</div>
<a href="/" class="mt-6 inline-block text-emerald-400 hover:text-emerald-300 text-sm">Back to Dashboard</a>
</main>
{{template "foot" .}}
{{end}}
{{define "dashboard"}}
{{template "head" .}}
<main class="max-w-6xl mx-auto p-6 space-y-6">
{{if .golden}}
<div class="bg-emerald-900/30 border border-emerald-800 rounded-lg p-4">
<div class="text-xs text-emerald-400 uppercase tracking-wider">Golden Thread</div>
<div class="text-lg font-semibold mt-1">{{.golden.Title}}</div>
<div class="text-sm text-gray-400">{{.golden.Motivation}}</div>
</div>
{{end}}
<div class="flex gap-4">
<input type="text" name="q" placeholder="Search knowledge index..."
class="flex-1 bg-gray-900 border border-gray-700 rounded px-4 py-2 text-sm focus:outline-none focus:border-emerald-500"
hx-get="/search" hx-trigger="keyup changed delay:300ms" hx-target="#results" hx-indicator="#search-indicator">
<button class="bg-emerald-700 hover:bg-emerald-600 px-4 py-2 rounded text-sm font-medium"
hx-get="/search" hx-include="[name=q]" hx-target="#results" hx-indicator="#search-indicator">Search</button>
</div>
<div id="results" class="text-sm text-gray-500"></div>
<div id="search-indicator" class="htmx-indicator text-xs text-emerald-400">Searching...</div>
<div hx-get="/stats" hx-trigger="load, every 30s" hx-swap="innerHTML">
<div class="text-gray-500">Loading stats...</div>
</div>
<section>
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Issues</h2>
{{if .gitea}}
<div class="space-y-1 mb-4">
{{range .gitea}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<span class="text-emerald-300">{{trunc .Title 80}}</span>
<span class="ml-2 text-xs text-gray-600">{{displayTime .}}</span>
</a>
{{end}}
</div>
{{end}}
</section>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<section>
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Notes</h3>
{{if .obsidian}}
<div class="space-y-1">
{{range .obsidian}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<span>{{trunc .Title 50}}</span>
<span class="ml-2 text-xs text-gray-600">{{displayTime .}}</span>
</a>
{{end}}
</div>
{{else}}
<div class="text-gray-500 text-xs">No notes indexed.</div>
{{end}}
</section>
<section>
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Sessions</h3>
{{if .session}}
<div class="space-y-1">
{{range .session}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<span>{{trunc .Title 50}}</span>
<span class="ml-2 text-xs text-gray-600">{{displayTime .}}</span>
</a>
{{end}}
</div>
{{else}}
<div class="text-gray-500 text-xs">No sessions tracked.</div>
{{end}}
</section>
<section>
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Skills</h3>
{{if .skills}}
<div class="space-y-1">
{{range .skills}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<span>{{trunc .Title 50}}</span>
<span class="ml-2 text-xs text-gray-600">{{displayTime .}}</span>
</a>
{{end}}
</div>
{{else}}
<div class="text-gray-500 text-xs">No skills indexed.</div>
{{end}}
</section>
</div>
<section>
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-2">Feed</h3>
<div class="space-y-1">
{{range .recent}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<span class="font-mono text-xs text-gray-500">{{shortFP .Fingerprint}}</span>
<span class="ml-2">{{trunc .Title 60}}</span>
<span class="ml-2 text-xs px-1.5 py-0.5 rounded bg-gray-800 text-gray-400">{{.SourceID}}</span>
<span class="ml-2 text-xs text-gray-600">{{displayTime .}}</span>
</a>
{{else}}
<div class="text-gray-500 text-sm">No entries yet.</div>
{{end}}
</div>
</section>
</main>
{{template "foot" .}}
{{end}}
{{define "search_results"}}
{{if .results}}
<div class="space-y-1">
<div class="text-xs text-gray-500 mb-2">{{len .results}} results for "{{.query}}"</div>
{{range .results}}
<a href="/entries/{{.Fingerprint}}" class="block bg-gray-900 hover:bg-gray-800 rounded px-3 py-2 text-sm">
<div>
<span class="font-mono text-xs text-gray-500">{{shortFP .Fingerprint}}</span>
<span class="ml-2 font-medium">{{trunc .Title 70}}</span>
<span class="ml-2 text-xs px-1.5 py-0.5 rounded bg-gray-800 text-gray-400">{{.SourceID}}</span>
</div>
{{if .Summary}}<div class="text-gray-500 text-xs mt-0.5 ml-12">{{trunc .Summary 120}}</div>{{end}}
</a>
{{end}}
</div>
{{else}}
<p class="text-gray-500 text-sm">No results for "{{.query}}".</p>
{{end}}
{{end}}
{{define "stats"}}
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
{{range .stats}}
<div class="bg-gray-900 rounded-lg p-3 border border-gray-800">
<div class="text-xs text-gray-500 uppercase tracking-wider">{{.Label}}</div>
<div class="text-xl font-semibold mt-1">{{.Value}}</div>
</div>
{{end}}
</div>
{{end}}
{{define "thread_list"}}
{{template "head" .}}
<main class="max-w-4xl mx-auto p-6 space-y-4">
<div class="flex gap-2 mb-4">
<a href="/threads" class="px-3 py-1 text-sm rounded {{if eq .status ""}}bg-emerald-700{{else}}bg-gray-800 hover:bg-gray-700{{end}}">All</a>
<a href="/threads?status=active" class="px-3 py-1 text-sm rounded {{if eq .status "active"}}bg-emerald-700{{else}}bg-gray-800 hover:bg-gray-700{{end}}">Active</a>
<a href="/threads?status=stalled" class="px-3 py-1 text-sm rounded {{if eq .status "stalled"}}bg-emerald-700{{else}}bg-gray-800 hover:bg-gray-700{{end}}">Stalled</a>
<a href="/threads?status=resolved" class="px-3 py-1 text-sm rounded {{if eq .status "resolved"}}bg-emerald-700{{else}}bg-gray-800 hover:bg-gray-700{{end}}">Resolved</a>
</div>
{{range .threads}}
<a href="/threads/{{.ID}}" class="block bg-gray-900 rounded-lg p-4 border border-gray-800 hover:border-gray-600">
<div class="flex items-center gap-2">
<span class="text-lg font-semibold">{{.Title}}</span>
<span class="text-xs px-1.5 py-0.5 rounded {{if eq .Status "active"}}bg-emerald-900 text-emerald-300{{else if eq .Status "resolved"}}bg-blue-900 text-blue-300{{else}}bg-gray-800 text-gray-400{{end}}">{{.Status}}</span>
<span class="text-xs text-gray-500">{{.Priority}}</span>
</div>
{{if .Motivation}}<div class="text-sm text-gray-400 mt-1">{{.Motivation}}</div>{{end}}
<div class="text-xs text-gray-600 mt-1">{{.EntryCount}} observations · {{humanTime .UpdatedAt}}</div>
</a>
{{else}}
<div class="text-gray-500 text-sm">No threads found.</div>
{{end}}
</main>
{{template "foot" .}}
{{end}}
{{define "thread_detail"}}
{{template "head" .}}
<main class="max-w-4xl mx-auto p-6 space-y-6">
<div class="bg-gray-900 rounded-lg p-4 border border-gray-800">
<div class="flex items-center gap-2">
<h2 class="text-xl font-bold">{{.thread.Title}}</h2>
{{if .isGolden}}<span class="text-xs bg-emerald-900 text-emerald-300 px-2 py-0.5 rounded">GOLDEN</span>{{end}}
</div>
<div class="flex gap-4 mt-2 text-sm text-gray-400">
<span>{{.thread.Status}}</span>
<span>{{.thread.Priority}}</span>
<span>Created {{humanTime .thread.CreatedAt}}</span>
</div>
{{if .thread.Motivation}}<div class="mt-3 text-gray-300"><strong>Motivation:</strong> {{.thread.Motivation}}</div>{{end}}
{{if ne .thread.Provenance "{}"}}<div class="mt-1 text-xs text-gray-500"><strong>Provenance:</strong> {{.thread.Provenance}}</div>{{end}}
</div>
{{if .provenance}}
<section>
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Provenance Graph</h3>
<div class="bg-gray-900 rounded-lg p-3 border border-gray-800 text-sm font-mono">
{{range .provenance}}
<div class="text-gray-400">└─ <a href="{{fpURL .Fingerprint}}" class="text-emerald-400 hover:text-emerald-300">{{shortFP .Fingerprint}}</a> <span class="text-gray-600">[{{.Relation}}]</span></div>
{{end}}
</div>
</section>
{{end}}
{{if .obs}}
<section>
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Observations ({{len .obs}})</h3>
<div class="space-y-1">
{{range .obs}}
<div class="bg-gray-900 rounded px-3 py-2 text-sm flex items-center gap-3">
<span class="font-mono text-xs text-gray-500">#{{.ID}}</span>
<span class="flex-1">{{trunc .Title 50}}</span>
<span class="text-xs text-gray-500">{{.SourceID}}</span>
{{if gt .Count 1}}<span class="text-xs text-amber-500" title="seen {{.Count}} times">×{{.Count}}</span>{{end}}
<span class="text-xs text-gray-600">{{humanTime .CollectedAt}}</span>
</div>
{{end}}
</div>
</section>
{{end}}
{{if .notes}}
<section>
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Notes</h3>
<div class="space-y-2">
{{range .notes}}
<div class="bg-gray-900 rounded p-3 border border-gray-800">
<div class="text-xs text-gray-500">{{humanTime .CreatedAt}}</div>
<div class="text-sm mt-1">{{.Note}}</div>
</div>
{{end}}
</div>
</section>
{{end}}
</main>
{{template "foot" .}}
{{end}}
{{define "entry_detail"}}
{{template "head" .}}
<main class="max-w-4xl mx-auto p-6 space-y-4">
<div class="bg-gray-900 rounded-lg p-4 border border-gray-800">
<div class="grid grid-cols-2 gap-4 text-sm">
<div><span class="text-gray-500">Fingerprint</span><br><span class="font-mono text-xs">{{.entry.Fingerprint}}</span></div>
<div><span class="text-gray-500">Source</span><br>{{.entry.SourceID}}</div>
<div><span class="text-gray-500">Title</span><br><span class="font-medium">{{.entry.Title}}</span></div>
<div><span class="text-gray-500">Project</span><br>{{.entry.Project}}</div>
<div><span class="text-gray-500">Path</span><br><span class="font-mono text-xs">{{.entry.SourcePath}}</span></div>
<div><span class="text-gray-500">Type</span><br>{{.entry.ContentType}}</div>
<div><span class="text-gray-500">Signal Time</span><br>{{if .entry.CreatedAt}}{{humanTime .entry.CreatedAt}}{{else}}<span class="text-gray-500">—</span>{{end}}</div>
<div><span class="text-gray-500">Ingested</span><br>{{humanTime .entry.LastSeen}}</div>
<div><span class="text-gray-500">Ref Count</span><br>{{.entry.RefCount}}</div>
<div><span class="text-gray-500">Confidence</span><br>{{printf "%.1f" .entry.LastConfidence}}</div>
</div>
{{if .entry.Summary}}<div class="mt-4 p-3 bg-gray-800 rounded text-sm">{{.entry.Summary}}</div>{{end}}
</div>
{{if .obs}}
<section>
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Observation History ({{len .obs}})</h3>
<div class="space-y-1">
{{range .obs}}
<div class="bg-gray-900 rounded px-3 py-2 text-sm flex items-center gap-3">
<span class="font-mono text-xs text-gray-500">#{{.ID}}</span>
<span class="text-xs text-gray-500">{{humanTime .CollectedAt}}</span>
{{if gt .Count 1}}<span class="text-xs text-amber-500" title="seen {{.Count}} times, unchanged">×{{.Count}}</span>{{end}}
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-800 text-gray-400">{{.Trigger}}</span>
<span class="text-xs text-gray-500">{{.IngesterVersion}}</span>
<span class="text-xs text-gray-500">conf:{{printf "%.1f" .Confidence}}</span>
</div>
{{end}}
</div>
</section>
{{end}}
</main>
{{template "foot" .}}
{{end}}