ontoref/crates/ontoref-daemon/templates/pages/personal.html
Jesús Pérez 472952e29b
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
feat: domain extension system, VCS abstraction, personal/provisioning domains, web subpages
Domain extension system (ADR-012): bash-layer dispatch activates repo_kind-conditional CLI
  domains. install.nu copies domains/ tree; short_alias wrappers generated (personal, prov).
  ore help and describe capabilities domain-aware.

  personal domain (PersonalOntology): career skills/talks/publications/positioning, CFP
  pipeline (Watching→Delivered), opportunities lifecycle, content pipeline, Sessionize
  integration. Daemon pages: /career, /personal.

  provisioning domain (DevWorkspace/Mixed): FSM state, next transitions, connections graph,
  gates, workspace card, capabilities, backlog. Daemon page: /provisioning.

  VCS abstraction layer (ADR-013): reflection/modules/vcs.nu — uniform jj/git API via
  filesystem detection (.jj/ vs .git/). opmode.nu and git-event.nu migrated off ^git.
  reflection/bin/jjw.nu — jj + ontoref + Radicle agent workspace lifecycle. jjw-ncl-merge.nu
  registered as jj merge tool for .ontology/ NCL conflicts. init-repo.nu for new_project mode.
  jj/rad not in ontoref requirements — belong in orchestration project manifests.

  'Framework RepoKind: ontology/schemas/manifest.ncl gains 'Framework variant; ontoref
  self-identifies as framework — no domain activates for the protocol itself.

  Web presence: personal.html and provisioning.html domain subpages. index.html gains
  "Project Types — Domain Extensions" section with type cards and subpage links. Nav
  compacted (Arch/Prov labels, solid backdrop-filter background).

  on+re: vcs-abstraction (adrs: adr-013) and agent-workspace-orchestration Practice nodes;
  21 manifest capabilities; state.ncl catalysts updated.
2026-04-07 23:08:29 +01:00

263 lines
12 KiB
HTML

{% extends "base.html" %}
{% import "macros/ui.html" as m %}
{% block title %}Personal — {{ slug }} — Ontoref{% endblock title %}
{% block nav_personal %}active{% endblock nav_personal %}
{% block nav_group_domain %}active{% endblock nav_group_domain %}
{% block content %}
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Personal Ontology</h1>
<p class="text-base-content/50 text-sm mt-1">Content pipeline · Opportunities · CFP pipeline</p>
</div>
</div>
<!-- Tabs -->
<div role="tablist" class="tabs tabs-bordered mb-4">
<a role="tab" class="tab tab-active" onclick="switchTab('content', this)">Content</a>
<a role="tab" class="tab" onclick="switchTab('opportunities', this)">Opportunities</a>
<a role="tab" class="tab" onclick="switchTab('cfp', this)">CFP Pipeline</a>
</div>
<!-- Content tab -->
<div id="tab-content">
<div class="flex flex-wrap gap-2 mb-3">
<input id="content-filter" type="text" placeholder="Filter by title, kind…"
class="input input-sm input-bordered flex-1 min-w-48 font-mono" oninput="renderContent()">
<select id="content-status" class="select select-sm select-bordered" onchange="renderContent()">
<option value="">All statuses</option>
<option value="Idea">Idea</option>
<option value="Brief">Brief</option>
<option value="Draft">Draft</option>
<option value="Review">Review</option>
<option value="Published">Published</option>
<option value="Archived">Archived</option>
</select>
</div>
<div class="overflow-x-auto">
<table class="table table-sm w-full bg-base-200 rounded-lg">
<thead>
<tr class="text-base-content/50 text-xs uppercase tracking-wider">
<th>ID</th><th>Title</th><th>Kind</th><th>Status</th><th>Audience</th>
</tr>
</thead>
<tbody id="content-body"></tbody>
</table>
</div>
</div>
<!-- Opportunities tab -->
<div id="tab-opportunities" class="hidden">
<div class="flex flex-wrap gap-2 mb-3">
<input id="opp-filter" type="text" placeholder="Filter by name, kind…"
class="input input-sm input-bordered flex-1 min-w-48 font-mono" oninput="renderOpportunities()">
<select id="opp-status" class="select select-sm select-bordered" onchange="renderOpportunities()">
<option value="">All statuses</option>
<option value="Watching">Watching</option>
<option value="Evaluating">Evaluating</option>
<option value="Active">Active</option>
<option value="Submitted">Submitted</option>
<option value="Closed">Closed</option>
</select>
</div>
<div class="overflow-x-auto">
<table class="table table-sm w-full bg-base-200 rounded-lg">
<thead>
<tr class="text-base-content/50 text-xs uppercase tracking-wider">
<th>ID</th><th>Name</th><th>Kind</th><th>Status</th><th>Deadline</th><th>Links</th>
</tr>
</thead>
<tbody id="opp-body"></tbody>
</table>
</div>
</div>
<!-- CFP tab -->
<div id="tab-cfp" class="hidden">
<div class="flex flex-wrap gap-2 mb-3">
<input id="cfp-filter" type="text" placeholder="Filter by name, note…"
class="input input-sm input-bordered flex-1 min-w-48 font-mono" oninput="renderCfp()">
<select id="cfp-stage" class="select select-sm select-bordered" onchange="renderCfp()">
<option value="">All stages</option>
<option value="Watching">Watching</option>
<option value="Evaluating">Evaluating</option>
<option value="Drafting">Drafting</option>
<option value="Submitted">Submitted</option>
<option value="Accepted">Accepted</option>
<option value="Declined">Declined</option>
<option value="Delivered">Delivered</option>
</select>
</div>
<div id="cfp-cards" class="grid grid-cols-1 lg:grid-cols-2 gap-4"></div>
</div>
<!-- Detail modal -->
<dialog id="detail-modal" class="modal">
<div class="modal-box w-full max-w-2xl">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3"></button>
</form>
<div id="modal-content" class="text-sm space-y-3"></div>
</div>
<form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
<script>
const CONTENTS = {{ contents_json | safe }};
const OPPORTUNITIES = {{ opportunities_json | safe }};
const CFP = {{ cfp_json | safe }};
const STATUS_BADGE = {
Idea: 'badge-ghost', Brief: 'badge-info', Draft: 'badge-warning',
Review: 'badge-warning', Published: 'badge-success', Rejected: 'badge-error',
Archived: 'badge-ghost',
Watching: 'badge-ghost', Evaluating: 'badge-info', Active: 'badge-warning',
Submitted: 'badge-info', Closed: 'badge-ghost',
Drafting: 'badge-warning', Accepted: 'badge-success', Declined: 'badge-error',
Delivered: 'badge-success',
};
function badge(s) {
const cls = STATUS_BADGE[s] ?? 'badge-ghost';
return `<span class="badge badge-xs font-mono ${cls}">${s}</span>`;
}
function linkChip(lk) {
return `<a href="${lk.url}" target="_blank" class="badge badge-xs badge-outline font-mono">${lk.kind}${lk.label ? ': ' + lk.label : ''}</a>`;
}
// ── Content ──────────────────────────────────────────────────────────────────
let visibleContents = CONTENTS;
function renderContent() {
const text = document.getElementById('content-filter').value.toLowerCase();
const status = document.getElementById('content-status').value;
visibleContents = CONTENTS.filter(c => {
const tm = !text || c.id.toLowerCase().includes(text) || (c.title||'').toLowerCase().includes(text) || (c.kind||'').toLowerCase().includes(text);
const sm = !status || c.status === status;
return tm && sm;
});
const tbody = document.getElementById('content-body');
tbody.innerHTML = visibleContents.length === 0
? '<tr><td colspan="5" class="text-center text-base-content/30 py-6">No content items</td></tr>'
: visibleContents.map((c, i) => `
<tr class="hover cursor-pointer" onclick="showContentDetail(${i})">
<td class="font-mono text-xs text-primary">${c.id}</td>
<td class="text-sm">${c.title || '<span class="text-base-content/30">—</span>'}</td>
<td class="font-mono text-xs">${c.kind}</td>
<td>${badge(c.status)}</td>
<td class="text-xs text-base-content/60">${c.audience || ''}</td>
</tr>`).join('');
}
function showContentDetail(i) {
const c = visibleContents[i];
if (!c) return;
const links = (c.links||[]).map(linkChip).join(' ');
document.getElementById('modal-content').innerHTML = `
<div class="flex items-baseline gap-2 flex-wrap">
<span class="font-mono font-bold text-primary">${c.id}</span>
${badge(c.status)} <span class="font-mono text-xs text-base-content/40">${c.kind}</span>
</div>
<h2 class="text-lg font-bold">${c.title || '—'}</h2>
<div class="flex gap-2 flex-wrap text-xs text-base-content/50">
${c.audience ? `<span>Audience: ${c.audience}</span>` : ''}
${(c.platforms||[]).length ? `<span>Platforms: ${c.platforms.join(', ')}</span>` : ''}
</div>
${links ? `<div class="flex flex-wrap gap-1">${links}</div>` : ''}
${c.note ? `<p class="text-base-content/70 text-sm leading-relaxed">${c.note}</p>` : ''}
${(c.linked_nodes||[]).length ? `<p class="text-xs text-base-content/40 font-mono">nodes: ${c.linked_nodes.join(', ')}</p>` : ''}
`;
document.getElementById('detail-modal').showModal();
}
// ── Opportunities ─────────────────────────────────────────────────────────────
let visibleOpps = OPPORTUNITIES;
function renderOpportunities() {
const text = document.getElementById('opp-filter').value.toLowerCase();
const status = document.getElementById('opp-status').value;
visibleOpps = OPPORTUNITIES.filter(o => {
const tm = !text || o.id.toLowerCase().includes(text) || (o.name||'').toLowerCase().includes(text) || (o.kind||'').toLowerCase().includes(text);
const sm = !status || o.status === status;
return tm && sm;
});
const tbody = document.getElementById('opp-body');
tbody.innerHTML = visibleOpps.length === 0
? '<tr><td colspan="6" class="text-center text-base-content/30 py-6">No opportunities</td></tr>'
: visibleOpps.map((o, i) => `
<tr class="hover cursor-pointer" onclick="showOppDetail(${i})">
<td class="font-mono text-xs text-primary">${o.id}</td>
<td class="text-sm">${o.name}</td>
<td class="font-mono text-xs">${o.kind}</td>
<td>${badge(o.status)}</td>
<td class="font-mono text-xs text-base-content/50">${o.deadline || '—'}</td>
<td class="text-xs">${(o.links||[]).length ? `<span class="badge badge-xs badge-outline">${(o.links||[]).length}</span>` : '—'}</td>
</tr>`).join('');
}
function showOppDetail(i) {
const o = visibleOpps[i];
if (!o) return;
const links = (o.links||[]).map(linkChip).join(' ');
document.getElementById('modal-content').innerHTML = `
<div class="flex items-baseline gap-2 flex-wrap">
<span class="font-mono font-bold text-primary">${o.id}</span>
${badge(o.status)} <span class="font-mono text-xs text-base-content/40">${o.kind}</span>
</div>
<h2 class="text-lg font-bold">${o.name}</h2>
${o.deadline ? `<p class="text-xs text-base-content/50 font-mono">Deadline: ${o.deadline}</p>` : ''}
${links ? `<div class="flex flex-wrap gap-1">${links}</div>` : ''}
${(o.fit_signals||[]).length ? `<p class="text-xs text-base-content/50">Fit: ${o.fit_signals.join(' · ')}</p>` : ''}
${o.note ? `<p class="text-base-content/70 text-sm leading-relaxed">${o.note}</p>` : ''}
${(o.linked_nodes||[]).length ? `<p class="text-xs text-base-content/40 font-mono">nodes: ${o.linked_nodes.join(', ')}</p>` : ''}
`;
document.getElementById('detail-modal').showModal();
}
// ── CFP ───────────────────────────────────────────────────────────────────────
function renderCfp() {
const text = document.getElementById('cfp-filter').value.toLowerCase();
const stage = document.getElementById('cfp-stage').value;
const items = CFP.filter(c => {
const tm = !text || c.id.toLowerCase().includes(text) || (c.name||'').toLowerCase().includes(text) || (c.note||'').toLowerCase().includes(text);
const sm = !stage || c.stage === stage;
return tm && sm;
});
const container = document.getElementById('cfp-cards');
container.innerHTML = items.length === 0
? '<p class="text-base-content/30 text-sm py-6 col-span-2">No CFP items</p>'
: items.map(c => {
const links = (c.links||[]).map(linkChip).join(' ');
return `
<div class="bg-base-200 rounded-lg p-4 space-y-2">
<div class="flex items-start justify-between gap-2">
<div>
<p class="font-semibold text-sm">${c.name}</p>
<p class="font-mono text-xs text-base-content/40">${c.id}</p>
</div>
${badge(c.stage)}
</div>
${c.deadline ? `<p class="text-xs text-base-content/50 font-mono">Deadline: ${c.deadline}</p>` : ''}
${links ? `<div class="flex flex-wrap gap-1">${links}</div>` : ''}
${c.next_action ? `<div class="border-t border-base-content/10 pt-2"><p class="text-xs text-base-content/50 uppercase tracking-wider mb-1">Next action</p><p class="text-xs text-base-content/70">${c.next_action}</p></div>` : ''}
${c.note ? `<p class="text-xs text-base-content/50 italic">${c.note}</p>` : ''}
</div>`;
}).join('');
}
// ── Tabs ──────────────────────────────────────────────────────────────────────
function switchTab(name, el) {
['content','opportunities','cfp'].forEach(t => {
document.getElementById('tab-' + t).classList.toggle('hidden', t !== name);
});
document.querySelectorAll('[role="tab"]').forEach(t => t.classList.remove('tab-active'));
el.classList.add('tab-active');
}
renderContent();
renderOpportunities();
renderCfp();
</script>
{% endblock content %}