Skip to content

Commit aabcd81

Browse files
committed
checkpoint: v3.5 + v3.5-alpha6 pages, OntologyGraph from TTL, browse tool, fixes
- docs/v3.5/: 21 spec pages (may need renaming to v3.5-alpha6) - docs/v3.5-alpha6/: 19 spec pages (duplicate from second agent) - OntologyGraph.vue: loads actual TTL files via N3.js, renders from parsed data - Landing page: version numbers removed from hero - Sidebar: Enterprise → Autonomous Operations fixed - whats-new.md created - URN resolver timing fixed - browse/ synced to public/browse/ - All 26 pages return HTTP 200
1 parent 7c4ba9f commit aabcd81

64 files changed

Lines changed: 12626 additions & 109 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
<template>
2+
<div class="onto-wrap" ref="wrap">
3+
<svg ref="svg" :viewBox="`0 0 ${W} ${H}`" xmlns="http://www.w3.org/2000/svg">
4+
<!-- DL Box headers -->
5+
<g v-for="(box, bi) in boxes" :key="bi">
6+
<rect :x="box.x - box.w/2" y="8" :width="box.w" height="26" rx="6"
7+
:fill="box.color" fill-opacity="0.1" :stroke="box.color" stroke-width="1" class="box-header"/>
8+
<text :x="box.x" y="26" text-anchor="middle" :fill="box.color"
9+
font-size="11" font-weight="700" font-family="system-ui" class="box-label">{{ box.label }}</text>
10+
</g>
11+
12+
<!-- Edges (drawn first, behind nodes) -->
13+
<line v-for="(edge, ei) in edges" :key="'e'+ei"
14+
:x1="edge.x1" :y1="edge.y1" :x2="edge.x2" :y2="edge.y2"
15+
:stroke="edge.color" stroke-opacity="0.15" stroke-width="1" class="edge"/>
16+
17+
<!-- Nodes -->
18+
<g v-for="(node, ni) in nodes" :key="'n'+ni" class="node-g"
19+
@mouseenter="hovered = ni" @mouseleave="hovered = null">
20+
<circle :cx="node.x" :cy="node.y" :r="hovered === ni ? 7 : 4.5"
21+
:fill="node.color" :fill-opacity="hovered === ni ? 1 : 0.7" class="dot"/>
22+
<text :x="node.x + 9" :y="node.y + (hovered === ni ? -2 : 4)"
23+
:fill="hovered === ni ? 'var(--vp-c-text-1)' : 'var(--vp-c-text-2)'"
24+
:font-size="hovered === ni ? 12 : 10" font-family="system-ui"
25+
:font-weight="hovered === ni ? 700 : 400" class="label">{{ node.label }}</text>
26+
<text v-if="hovered === ni" :x="node.x + 9" :y="node.y + 12"
27+
fill="var(--vp-c-text-3)" font-size="8" font-family="system-ui" class="sublabel">{{ node.type }}</text>
28+
</g>
29+
30+
<!-- Loading indicator -->
31+
<text v-if="loading" :x="W/2" :y="H - 10" text-anchor="middle"
32+
fill="var(--vp-c-text-3)" font-size="9" font-family="system-ui">
33+
loading ontology... {{ loadedCount }}/7
34+
</text>
35+
</svg>
36+
</div>
37+
</template>
38+
39+
<script setup>
40+
import { ref, onMounted, onUnmounted } from 'vue'
41+
42+
const W = 920
43+
const H = 400
44+
const svg = ref(null)
45+
const wrap = ref(null)
46+
const hovered = ref(null)
47+
const loading = ref(true)
48+
const loadedCount = ref(0)
49+
const nodes = ref([])
50+
const edges = ref([])
51+
52+
const boxes = [
53+
{ label: 'TBox · Identity', x: 160, w: 180, color: '#0B6E2D' },
54+
{ label: 'RBox · Capability', x: 460, w: 180, color: '#B8860B' },
55+
{ label: 'ABox · Knowledge', x: 760, w: 180, color: '#6B21A8' },
56+
]
57+
58+
// Map BFO types to DL boxes
59+
const boxMap = {
60+
'Class': 0, 'Ontology': 0,
61+
'Property': 1,
62+
'Individual': 2,
63+
}
64+
65+
// Color by BFO category
66+
const colorMap = {
67+
0: '#0B6E2D', // TBox green
68+
1: '#B8860B', // RBox amber
69+
2: '#6B21A8', // ABox purple
70+
}
71+
72+
let animeInstances = []
73+
74+
onMounted(async () => {
75+
if (typeof window === 'undefined') return
76+
77+
// Load N3.js dynamically
78+
let N3
79+
try {
80+
const script = document.createElement('script')
81+
script.src = 'https://cdn.jsdelivr.net/npm/n3@1.16.3/browser/n3.min.js'
82+
document.head.appendChild(script)
83+
await new Promise((resolve, reject) => {
84+
script.onload = resolve
85+
script.onerror = reject
86+
})
87+
N3 = window.N3
88+
} catch (e) {
89+
loading.value = false
90+
buildFallbackGraph()
91+
return
92+
}
93+
94+
// Fetch and parse TTL files
95+
const ttlFiles = [
96+
'/ontology/v3.5-alpha6/core.ttl',
97+
'/ontology/v3.5-alpha6/kernel-metadata.ttl',
98+
'/ontology/v3.5-alpha6/processes.ttl',
99+
'/ontology/v3.5-alpha6/relations.ttl',
100+
'/ontology/v3.5-alpha6/base-instances.ttl',
101+
'/ontology/v3.5-alpha6/proof.ttl',
102+
'/ontology/v3.5-alpha6/rbac.ttl',
103+
]
104+
105+
const store = new N3.Store()
106+
const CLASS_TYPES = [
107+
'http://www.w3.org/2002/07/owl#Class',
108+
'http://www.w3.org/2000/01/rdf-schema#Class',
109+
]
110+
const PROP_TYPES = [
111+
'http://www.w3.org/2002/07/owl#ObjectProperty',
112+
'http://www.w3.org/2002/07/owl#DatatypeProperty',
113+
]
114+
115+
for (const url of ttlFiles) {
116+
try {
117+
const resp = await fetch(url)
118+
if (!resp.ok) continue
119+
const text = await resp.text()
120+
const parser = new N3.Parser({ baseIRI: 'https://conceptkernel.org/ontology/v3.5/' })
121+
const quads = parser.parse(text)
122+
store.addQuads(quads)
123+
loadedCount.value++
124+
} catch (e) { /* skip failed files */ }
125+
}
126+
127+
loading.value = false
128+
129+
// Extract classes and properties
130+
const classes = new Set()
131+
const properties = new Set()
132+
const subClassEdges = []
133+
134+
for (const q of store.getQuads(null, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', null)) {
135+
if (CLASS_TYPES.includes(q.object.value)) classes.add(q.subject.value)
136+
if (PROP_TYPES.includes(q.object.value)) properties.add(q.subject.value)
137+
}
138+
139+
for (const q of store.getQuads(null, 'http://www.w3.org/2000/01/rdf-schema#subClassOf', null)) {
140+
if (classes.has(q.subject.value) && classes.has(q.object.value)) {
141+
subClassEdges.push({ from: q.subject.value, to: q.object.value })
142+
}
143+
}
144+
145+
// Build node list — pick the most interesting classes (not all 507)
146+
const allEntities = []
147+
classes.forEach(uri => {
148+
const localName = uri.includes('#') ? uri.split('#').pop() : uri.split('/').pop()
149+
if (localName && localName.length > 1 && !localName.startsWith('_'))
150+
allEntities.push({ uri, label: localName, type: 'Class', box: 0 })
151+
})
152+
properties.forEach(uri => {
153+
const localName = uri.includes('#') ? uri.split('#').pop() : uri.split('/').pop()
154+
if (localName && localName.length > 1 && !localName.startsWith('_'))
155+
allEntities.push({ uri, label: localName, type: 'Property', box: 1 })
156+
})
157+
158+
// Assign boxes and layout
159+
const grouped = { 0: [], 1: [], 2: [] }
160+
allEntities.forEach(e => {
161+
// Override box based on known patterns
162+
if (e.label.includes('Instance') || e.label.includes('Proof') || e.label.includes('Ledger') || e.label.includes('Project'))
163+
e.box = 2
164+
else if (e.label.includes('Contract') || e.label.includes('Serving') || e.label.includes('Edge') || e.label.includes('Action') || e.label.includes('Process') || e.type === 'Property')
165+
e.box = 1
166+
167+
if (grouped[e.box].length < 14) // Cap per column
168+
grouped[e.box].push(e)
169+
})
170+
171+
// Position nodes
172+
const positioned = []
173+
Object.entries(grouped).forEach(([boxIdx, items]) => {
174+
const bx = boxes[parseInt(boxIdx)].x
175+
const startY = 50
176+
const spacing = (H - 70) / Math.max(items.length, 1)
177+
items.forEach((item, i) => {
178+
const jitter = (Math.random() - 0.5) * 40
179+
positioned.push({
180+
...item,
181+
x: bx + jitter,
182+
y: startY + i * spacing + Math.random() * 10,
183+
color: colorMap[parseInt(boxIdx)],
184+
})
185+
})
186+
})
187+
188+
nodes.value = positioned
189+
190+
// Build edges from subclass relationships
191+
const nodeMap = {}
192+
positioned.forEach((n, i) => { nodeMap[n.uri] = i })
193+
194+
const edgeList = []
195+
subClassEdges.forEach(({ from, to }) => {
196+
if (nodeMap[from] !== undefined && nodeMap[to] !== undefined) {
197+
const f = positioned[nodeMap[from]]
198+
const t = positioned[nodeMap[to]]
199+
edgeList.push({ x1: f.x, y1: f.y, x2: t.x, y2: t.y, color: f.color })
200+
}
201+
})
202+
edges.value = edgeList
203+
204+
// Animate
205+
startAnimations()
206+
})
207+
208+
function buildFallbackGraph() {
209+
// Static fallback if N3.js fails to load
210+
const fallback = [
211+
{ label: 'Kernel', type: 'MaterialEntity', box: 0 },
212+
{ label: 'KernelOntology', type: 'Document', box: 0 },
213+
{ label: 'GovernanceMode', type: 'Quality', box: 0 },
214+
{ label: 'Action', type: 'PlanSpec', box: 1 },
215+
{ label: 'Edge', type: 'Artifact', box: 1 },
216+
{ label: 'ServingDisposition', type: 'Disposition', box: 1 },
217+
{ label: 'Instance', type: 'DataItem', box: 2 },
218+
{ label: 'ProofRecord', type: 'Proof', box: 2 },
219+
{ label: 'LedgerEntry', type: 'Ledger', box: 2 },
220+
]
221+
fallback.forEach((item, i) => {
222+
const bx = boxes[item.box].x
223+
nodes.value.push({
224+
...item,
225+
x: bx + (Math.random() - 0.5) * 30,
226+
y: 70 + (i % 3) * 80 + Math.random() * 20,
227+
color: colorMap[item.box],
228+
})
229+
})
230+
startAnimations()
231+
}
232+
233+
async function startAnimations() {
234+
if (typeof window === 'undefined' || !svg.value) return
235+
try {
236+
const anime = (await import('animejs/lib/anime.es.js')).default
237+
238+
// Fade in dots
239+
animeInstances.push(anime({
240+
targets: svg.value.querySelectorAll('.dot'),
241+
opacity: [0, 0.7],
242+
r: [0, 4.5],
243+
delay: anime.stagger(40, { start: 200 }),
244+
duration: 600,
245+
easing: 'easeOutElastic(1, .6)'
246+
}))
247+
248+
// Fade in labels
249+
animeInstances.push(anime({
250+
targets: svg.value.querySelectorAll('.label'),
251+
opacity: [0, 1],
252+
translateX: [-6, 0],
253+
delay: anime.stagger(40, { start: 400 }),
254+
duration: 400,
255+
easing: 'easeOutQuad'
256+
}))
257+
258+
// Draw edges
259+
animeInstances.push(anime({
260+
targets: svg.value.querySelectorAll('.edge'),
261+
strokeDashoffset: [anime.setDashoffset, 0],
262+
opacity: [0, 0.15],
263+
delay: anime.stagger(30, { start: 300 }),
264+
duration: 800,
265+
easing: 'easeInOutQuad'
266+
}))
267+
268+
// Continuous breathing
269+
animeInstances.push(anime({
270+
targets: svg.value.querySelectorAll('.dot'),
271+
r: [4.5, 6, 4.5],
272+
fillOpacity: [0.7, 0.9, 0.7],
273+
duration: 4000,
274+
loop: true,
275+
easing: 'easeInOutSine',
276+
delay: anime.stagger(150)
277+
}))
278+
279+
// Box header pulse
280+
animeInstances.push(anime({
281+
targets: svg.value.querySelectorAll('.box-header'),
282+
fillOpacity: [0.08, 0.15, 0.08],
283+
duration: 5000,
284+
loop: true,
285+
easing: 'easeInOutSine',
286+
delay: anime.stagger(500)
287+
}))
288+
289+
} catch (e) { /* anime.js unavailable */ }
290+
}
291+
292+
onUnmounted(() => {
293+
animeInstances.forEach(a => a && a.pause && a.pause())
294+
})
295+
</script>
296+
297+
<style scoped>
298+
.onto-wrap {
299+
max-width: 920px;
300+
margin: 0.5rem auto 1rem;
301+
padding: 0 0.5rem;
302+
}
303+
.onto-wrap svg {
304+
width: 100%;
305+
height: auto;
306+
min-height: 300px;
307+
}
308+
.node-g { cursor: default; transition: opacity 0.2s; }
309+
.node-g:hover .dot { filter: brightness(1.3); }
310+
311+
@media (max-width: 640px) {
312+
.onto-wrap svg { min-height: 200px; }
313+
.label { font-size: 8px !important; }
314+
}
315+
</style>

0 commit comments

Comments
 (0)