96 lines
3.5 KiB
HTML
96 lines
3.5 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
|
||
|
|
{% block title %}Positioning Roadmap — Ontoref{% endblock title %}
|
||
|
|
{% block nav_group_knowledge %}active{% endblock nav_group_knowledge %}
|
||
|
|
{% block main_class %}container mx-auto px-4 py-6{% endblock main_class %}
|
||
|
|
|
||
|
|
{% block head %}
|
||
|
|
<link rel="stylesheet" href="{{ base_url }}/assets/vendor/vis-timeline.min.css" />
|
||
|
|
<style>
|
||
|
|
#roadmap-root {
|
||
|
|
width: 100%;
|
||
|
|
height: calc(100vh - 240px);
|
||
|
|
min-height: 380px;
|
||
|
|
border-radius: 0.5rem;
|
||
|
|
overflow: hidden;
|
||
|
|
border: 1px solid oklch(var(--bc) / 0.12);
|
||
|
|
}
|
||
|
|
.vis-item { border-radius: 4px; font-size: 12px; }
|
||
|
|
.vis-item.orphan { opacity: 0.45; border-style: dashed; }
|
||
|
|
.vis-label { font-size: 12px; color: oklch(var(--bc) / 0.85); }
|
||
|
|
.vis-timeline { background: oklch(var(--b1)); color: oklch(var(--bc)); }
|
||
|
|
.vis-time-axis .vis-text { color: oklch(var(--bc) / 0.6); }
|
||
|
|
.vis-panel.vis-center, .vis-panel.vis-left { border-color: oklch(var(--bc) / 0.12); }
|
||
|
|
#orphan-warn {
|
||
|
|
padding: 0.5rem 1rem; background: oklch(var(--wa) / 0.15);
|
||
|
|
border: 1px solid oklch(var(--wa) / 0.4); border-radius: 0.5rem;
|
||
|
|
font-size: 0.75rem; color: oklch(var(--wac));
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
{% endblock head %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="flex items-center justify-between mb-4 gap-4">
|
||
|
|
<h1 class="text-xl font-semibold">Positioning Roadmap</h1>
|
||
|
|
<button class="btn btn-ghost btn-sm" onclick="timeline.fit()">Fit</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{% if orphan_count > 0 %}
|
||
|
|
<div id="orphan-warn" class="mb-3">
|
||
|
|
⚠ {{ orphan_count }} milestone(s) don't anchor any differentiator — run
|
||
|
|
<code>onre coder chronicle --as-proof-candidates</code> to generate proof candidates.
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
|
||
|
|
<div id="roadmap-root"></div>
|
||
|
|
|
||
|
|
<div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
||
|
|
{% for m in mechanisms %}
|
||
|
|
<div class="card bg-base-200 p-3">
|
||
|
|
<p class="font-mono text-xs opacity-60">{{ m.id }}</p>
|
||
|
|
<p class="font-medium text-sm mt-0.5">{{ m.label | default(value=m.id) }}</p>
|
||
|
|
<p class="text-xs opacity-60 mt-1">{{ m.status | default(value="—") }}</p>
|
||
|
|
</div>
|
||
|
|
{% endfor %}
|
||
|
|
</div>
|
||
|
|
{% endblock content %}
|
||
|
|
|
||
|
|
{% block scripts %}
|
||
|
|
<script src="{{ base_url }}/assets/vendor/vis-timeline.min.js"></script>
|
||
|
|
<script>
|
||
|
|
const DATA = {{ roadmap_json | safe }};
|
||
|
|
const orphanIds = new Set((DATA.orphan_milestones || []).map(o => o.id));
|
||
|
|
|
||
|
|
const groups = (DATA.mechanisms || []).map(m => ({
|
||
|
|
id: m.id,
|
||
|
|
content: `<span class="text-xs font-mono" title="${m.label||m.id}">${m.id}</span>`,
|
||
|
|
}));
|
||
|
|
if (!groups.length) groups.push({ id: "__unassigned", content: "unassigned" });
|
||
|
|
|
||
|
|
const items = (DATA.milestones || []).map((m, i) => {
|
||
|
|
const start = m.date || m.target_date || "1970-01-01";
|
||
|
|
const grp = m.mechanism || m.refs?.[0] || "__unassigned";
|
||
|
|
const isOrphan = orphanIds.has(m.id);
|
||
|
|
return {
|
||
|
|
id: m.id || String(i),
|
||
|
|
group: grp,
|
||
|
|
start,
|
||
|
|
content: `<span style="max-width:200px;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${m.title}</span>`,
|
||
|
|
title: `${m.title}${isOrphan ? " ⚠ orphan" : ""}`,
|
||
|
|
className: isOrphan ? "orphan" : "",
|
||
|
|
style: isOrphan ? "background:#6b7280;border-color:#6b7280;color:#fff;" : "background:#f59e0b;border-color:#f59e0b;color:#000;",
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
const container = document.getElementById('roadmap-root');
|
||
|
|
const timeline = new vis.Timeline(container, new vis.DataSet(items), new vis.DataSet(groups), {
|
||
|
|
stack: true,
|
||
|
|
orientation: { axis: "top" },
|
||
|
|
selectable: false,
|
||
|
|
zoomMin: 1000 * 60 * 60 * 24 * 14,
|
||
|
|
zoomMax: 1000 * 60 * 60 * 24 * 365 * 5,
|
||
|
|
});
|
||
|
|
timeline.fit();
|
||
|
|
</script>
|
||
|
|
{% endblock scripts %}
|