156 lines
6.1 KiB
HTML
156 lines
6.1 KiB
HTML
|
|
{% extends "base.html" %}
|
||
|
|
{% import "macros/ui.html" as m %}
|
||
|
|
|
||
|
|
{% block title %}ADRs — {{ slug }} — Ontoref{% endblock title %}
|
||
|
|
{% block nav_adrs %}active{% endblock nav_adrs %}
|
||
|
|
{% block nav_group_dev %}active{% endblock nav_group_dev %}
|
||
|
|
|
||
|
|
{% block head %}
|
||
|
|
<style>
|
||
|
|
.status-accepted { @apply badge badge-success badge-xs font-mono; }
|
||
|
|
.status-proposed { @apply badge badge-warning badge-xs font-mono; }
|
||
|
|
.status-deprecated { @apply badge badge-ghost badge-xs font-mono; }
|
||
|
|
.status-superseded { @apply badge badge-error badge-xs font-mono; }
|
||
|
|
.status-error { @apply badge badge-error badge-xs font-mono; }
|
||
|
|
</style>
|
||
|
|
{% endblock head %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="mb-6 flex items-center justify-between">
|
||
|
|
<div>
|
||
|
|
<h1 class="text-2xl font-bold">Architecture Decision Records</h1>
|
||
|
|
<p class="text-base-content/50 text-sm mt-1">Typed NCL records — constraints, rationale, and alternatives for lasting architectural decisions</p>
|
||
|
|
</div>
|
||
|
|
<span class="badge badge-lg badge-neutral">{{ adr_count }} ADRs</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Filter bar -->
|
||
|
|
<div class="flex flex-wrap gap-2 mb-4">
|
||
|
|
<input id="filter-input" type="text" placeholder="Filter by ID, title, or context…"
|
||
|
|
class="input input-sm input-bordered flex-1 min-w-48 font-mono"
|
||
|
|
oninput="filterAdrs()">
|
||
|
|
<select id="filter-status" class="select select-sm select-bordered" onchange="filterAdrs()">
|
||
|
|
<option value="">All statuses</option>
|
||
|
|
<option value="Accepted">Accepted</option>
|
||
|
|
<option value="Proposed">Proposed</option>
|
||
|
|
<option value="Deprecated">Deprecated</option>
|
||
|
|
<option value="Superseded">Superseded</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ADR table -->
|
||
|
|
<div class="overflow-x-auto" id="adrs-container">
|
||
|
|
<table class="table table-sm w-full bg-base-200 rounded-lg" id="adrs-table">
|
||
|
|
<thead>
|
||
|
|
<tr class="text-base-content/50 text-xs uppercase tracking-wider">
|
||
|
|
<th class="w-24">ID</th>
|
||
|
|
<th>Title</th>
|
||
|
|
<th class="w-24">Status</th>
|
||
|
|
<th class="w-20">Date</th>
|
||
|
|
<th class="w-20">Constraints</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody id="adrs-body"></tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ADR detail modal -->
|
||
|
|
<dialog id="adr-modal" class="modal">
|
||
|
|
<div class="modal-box w-full max-w-3xl">
|
||
|
|
<form method="dialog">
|
||
|
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3">✕</button>
|
||
|
|
</form>
|
||
|
|
<div class="flex items-baseline gap-3 mb-1 pr-8">
|
||
|
|
<span id="detail-id" class="font-mono font-bold text-primary"></span>
|
||
|
|
<span id="detail-status-badge"></span>
|
||
|
|
<span id="detail-date" class="text-xs text-base-content/40 font-mono"></span>
|
||
|
|
</div>
|
||
|
|
<h2 id="detail-title" class="text-lg font-bold mb-3"></h2>
|
||
|
|
|
||
|
|
<div class="space-y-4 text-sm">
|
||
|
|
<div>
|
||
|
|
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-1">Context</h3>
|
||
|
|
<p id="detail-context" class="text-base-content/80 leading-relaxed"></p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-1">Decision</h3>
|
||
|
|
<p id="detail-decision" class="text-base-content/80 leading-relaxed"></p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex gap-4 text-xs text-base-content/40 border-t border-base-content/10 pt-3 mt-4">
|
||
|
|
<span>Hard constraints: <span id="detail-hard" class="font-mono text-error"></span></span>
|
||
|
|
<span>Soft constraints: <span id="detail-soft" class="font-mono text-warning"></span></span>
|
||
|
|
<span class="ml-auto font-mono" id="detail-file"></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<form method="dialog" class="modal-backdrop">
|
||
|
|
<button>close</button>
|
||
|
|
</form>
|
||
|
|
</dialog>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
const ADRS = {{ adrs_json | safe }};
|
||
|
|
|
||
|
|
function statusClass(s) {
|
||
|
|
const map = { Accepted: 'accepted', Proposed: 'proposed', Deprecated: 'deprecated', Superseded: 'superseded', Error: 'error' };
|
||
|
|
return `status-${map[s] || 'proposed'}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function statusBadge(s) {
|
||
|
|
return `<span class="${statusClass(s)}">${s}</span>`;
|
||
|
|
}
|
||
|
|
|
||
|
|
let visibleAdrs = ADRS;
|
||
|
|
|
||
|
|
function renderAdrs(adrs) {
|
||
|
|
visibleAdrs = adrs;
|
||
|
|
const tbody = document.getElementById('adrs-body');
|
||
|
|
tbody.innerHTML = adrs.map((a, i) => `
|
||
|
|
<tr class="hover cursor-pointer" onclick="showDetail(${i})">
|
||
|
|
<td class="font-mono text-xs text-primary">${a.id}</td>
|
||
|
|
<td class="text-sm">${a.title || '<span class="text-base-content/30">—</span>'}</td>
|
||
|
|
<td>${statusBadge(a.status)}</td>
|
||
|
|
<td class="font-mono text-xs text-base-content/50">${a.date || ''}</td>
|
||
|
|
<td class="text-xs">
|
||
|
|
${a.hard_constraints > 0 ? `<span class="text-error font-mono mr-1">${a.hard_constraints}H</span>` : ''}
|
||
|
|
${a.soft_constraints > 0 ? `<span class="text-warning font-mono">${a.soft_constraints}S</span>` : ''}
|
||
|
|
${a.hard_constraints === 0 && a.soft_constraints === 0 ? '<span class="text-base-content/30">—</span>' : ''}
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
`).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function showDetail(index) {
|
||
|
|
const a = visibleAdrs[index];
|
||
|
|
if (!a) return;
|
||
|
|
document.getElementById('detail-id').textContent = a.id;
|
||
|
|
document.getElementById('detail-status-badge').innerHTML = statusBadge(a.status);
|
||
|
|
document.getElementById('detail-date').textContent = a.date;
|
||
|
|
document.getElementById('detail-title').textContent = a.title;
|
||
|
|
document.getElementById('detail-context').textContent = a.context;
|
||
|
|
document.getElementById('detail-decision').textContent = a.decision;
|
||
|
|
document.getElementById('detail-hard').textContent = a.hard_constraints;
|
||
|
|
document.getElementById('detail-soft').textContent = a.soft_constraints;
|
||
|
|
document.getElementById('detail-file').textContent = a.file + '.ncl';
|
||
|
|
document.getElementById('adr-modal').showModal();
|
||
|
|
}
|
||
|
|
|
||
|
|
function filterAdrs() {
|
||
|
|
const text = document.getElementById('filter-input').value.toLowerCase();
|
||
|
|
const status = document.getElementById('filter-status').value;
|
||
|
|
const filtered = ADRS.filter(a => {
|
||
|
|
const textMatch = !text ||
|
||
|
|
a.id.toLowerCase().includes(text) ||
|
||
|
|
a.title.toLowerCase().includes(text) ||
|
||
|
|
a.context.toLowerCase().includes(text);
|
||
|
|
const statusMatch = !status || a.status === status;
|
||
|
|
return textMatch && statusMatch;
|
||
|
|
});
|
||
|
|
renderAdrs(filtered);
|
||
|
|
}
|
||
|
|
|
||
|
|
renderAdrs(ADRS);
|
||
|
|
</script>
|
||
|
|
{% endblock content %}
|