diff --git a/static/opax.html b/static/opax.html
index 0657e84..fa29af6 100644
--- a/static/opax.html
+++ b/static/opax.html
@@ -243,6 +243,7 @@
.md-viewer p{margin-bottom:12px;color:var(--text-dim)}
.md-viewer code{font-family:var(--mono);font-size:12px;background:rgba(255,255,255,.06);padding:2px 6px;border-radius:4px}
.md-viewer pre{background:var(--bg-2);border:1px solid var(--border);padding:14px 16px;border-radius:10px;overflow-x:auto;font-family:var(--mono);font-size:12px;margin:12px 0;line-height:1.6}
+ .md-viewer pre code{background:none;padding:0}
.md-viewer table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13px}
.md-viewer th{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-faint)}
.md-viewer td{padding:9px 12px;border-bottom:1px solid var(--border);color:var(--text-dim)}
@@ -251,6 +252,8 @@
.md-viewer li{margin-bottom:4px}
.md-viewer blockquote{border-left:3px solid var(--accent);padding:8px 16px;margin:12px 0;background:rgba(124,92,255,.06);border-radius:0 8px 8px 0;color:var(--text-dim)}
.md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0}
+ .md-viewer a{color:var(--accent);text-decoration:none}
+ .md-viewer a:hover{text-decoration:underline}
.md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px}
/* PRICING PANEL (CG4d) */
@@ -296,6 +299,7 @@
.browser-error.show{display:flex}
+
@@ -615,6 +619,9 @@ const STORAGE_KEY = 'jason_history_v2'
const SESSION_KEY = 'jason_session_v2'
const GH_BLOB = 'https://github.com/vauco-saas/OSVauco/blob/main/'
+// Configure marked.js once
+marked.setOptions({ gfm: true, breaks: true })
+
const MD_DOCS = [
{ icon:'ποΈ', name:'MASTERPLAN', path:'docs/MASTERPLAN.md', desc:'Autoritativ sannhet β vinner alltid ved konflikt', badge:'autoritativ', badgeCls:'auth' },
{ icon:'πΊοΈ', name:'ROADMAP', path:'docs/ROADMAP.md', desc:'Hva som skjer akkurat nΓ₯ β prioriteringer per sprint', badge:'aktiv', badgeCls:'' },
@@ -767,7 +774,9 @@ function browserFrameError(){
async function pollBuild(){
try{
const d = await fetch('/opax/build-status').then(r=>r.json())
- const st = (d.status||'').toUpperCase()
+ const builds = d.builds || []
+ const latest = builds[0] || {}
+ const st = (latest.status || d.status || '').toUpperCase()
const cls = st==='SUCCESS'?'ok':['FAILURE','TIMEOUT','CANCELLED'].includes(st)?'fail':st?'running':''
;['sb-build-dot','tb-build-dot'].forEach(id=>{ document.getElementById(id).className=(id==='sb-build-dot'?'build-dot ':'build-pill-dot ')+cls })
document.getElementById('tb-build-txt').textContent = 'Build: '+(st||'ukjent')
@@ -803,7 +812,7 @@ async function loadBilling(){
h+='
β οΈ Anomalier
'
anoms.slice(0,5).forEach(a=>{
const sev=(a.severity||'').toLowerCase()
- h+=`
${sev==='high'?'π΄':'π‘'}
${esc(a.service||a.type||'Ukjent')}
${esc(a.description||a.message||'')}
${esc(a.severity||'medium')}
`
+ h+=`
${sev==='high'?'π΄':'π‘'}
${esc(a.service||a.type||'Ukjent')}
${esc(a.description||a.message||'')}
${esc(a.severity||'medium')}
`
})
h+='
'
}else{ h+='
β
Ingen anomalier
' }
@@ -870,16 +879,20 @@ async function loadBuild(){
el.innerHTML='
'
const d=await pollBuild()
if(!d){el.innerHTML='
β
Kunne ikke hente build
';return}
- const st=(d.status||'').toUpperCase()
- const ico=st==='SUCCESS'?'β
':['FAILURE','TIMEOUT'].includes(st)?'β':'β³'
- document.getElementById('build-panel-sub').innerHTML=`
${st}`
- let h=`
${ico}
${esc(d.build_id||d.id||'β')}
${esc(d.branch||d.trigger||'main')}
${esc(d.created||d.timestamp||'β')}
`
- const logs=d.log_lines||d.logs||[]
- if(logs.length){
- h+='
'
- logs.forEach(l=>{ const c=/error|fail/i.test(l)?'ll-fail':/success|done/i.test(l)?'ll-ok':''; h+=`
${esc(l)}
` })
- h+='
'
+ const builds = d.builds || []
+ if(!builds.length){
+ el.innerHTML='
'
+ return
}
+ let h='
'
+ builds.forEach(b=>{
+ const st=(b.status||'').toUpperCase()
+ const ico=st==='SUCCESS'?'β
':['FAILURE','TIMEOUT'].includes(st)?'β':'β³'
+ h+=`
${ico}
${esc(b.id||'β')}
${esc(b.branch||'main')}
${esc(b.createTime||'β')}
`
+ })
+ h+='
'
+ const latest=builds[0]
+ document.getElementById('build-panel-sub').innerHTML=`
${latest.status||'ukjent'}`
el.innerHTML=h
}
@@ -916,7 +929,7 @@ function openInline(url, name){
document.getElementById('tb-sub').textContent=url
}
-// MD MODULE
+// MD MODULE β Fix 2: marked.js rendering
function renderMdGrid(){
const g=document.getElementById('md-grid')
if(g.children.length) return
@@ -950,11 +963,11 @@ async function openMd(path, name){
const viewer=document.getElementById('md-viewer')
viewer.innerHTML='
β³ Laster '+esc(path)+'...
'
try{
- // OQ-30: bruk /docs/ proxy i stedet for GH_RAW
const res = await fetch('/docs/'+path+'?t='+Date.now())
if(!res.ok) throw new Error('HTTP '+res.status)
const raw = await res.text()
- viewer.innerHTML=renderMd(raw)
+ // Fix 2: bruk marked.js i stedet for hjemmelaget renderMd()
+ viewer.innerHTML = marked.parse(raw)
}catch(e){
viewer.innerHTML=`
β Kunne ikke laste: ${esc(e.message)}
`
}
@@ -969,36 +982,6 @@ function mdShowGrid(){
document.getElementById('tb-sub').textContent='Kjerndoks Β· vauco-saas/OSVauco'
}
-function renderMd(text){
- let s=esc(text)
- s=s.replace(/```[^\n]*\n([\s\S]*?)```/g,'
$1
')
- s=s.replace(/`([^`]+)`/g,'
$1')
- s=s.replace(/^### (.+)$/gm,'
$1
')
- s=s.replace(/^## (.+)$/gm,'
$1
')
- s=s.replace(/^# (.+)$/gm,'
$1
')
- s=s.replace(/\*\*(.+?)\*\*/g,'
$1')
- s=s.replace(/^---$/gm,'
')
- s=s.replace(/^> (.+)$/gm,'
$1
')
- s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{
- const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r))
- if(!rows.length) return tbl
- let out='
'
- rows.forEach((r,i)=>{
- const cells=r.split('|').slice(1,-1)
- out+=i===0?'':'
'
- cells.forEach(c=>{ out+=i===0?`| ${c.trim()} | `:`${c.trim()} | ` })
- out+=i===0?'
':''
- })
- out+='
'
- return out
- })
- s=s.replace(/^[-*] (.+)$/gm,'
$1')
- s=s.replace(/(
.*<\/li>)/s, m=>'')
- s=s.replace(/^\d+\. (.+)$/gm,'$1')
- s=s.replace(/^(?!<[htublp|]).+$/gm, line=>line.trim()?`
${line}
`:'')
- return s
-}
-
// TERMINAL
let termInited=false
function initTerminal(){