docs: mark M10 complete and pass TEST:M10 gate (fix lint)

All M10 tasks (M10.1–M10.12) implemented and verified.
74 tests pass, 0 typecheck errors, 0 lint errors.
Fixed CodeRunner.vue script tag string literal lint issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 00:05:59 +00:00
co-authored by Claude Opus 4.6
parent f4270424f8
commit 869db87fd5
2 changed files with 33 additions and 39 deletions
+13 -13
View File
@@ -33,19 +33,19 @@
## M10: Advanced Content Renderers
- [ ] **M10.1** — Full article renderer (TOC sidebar, reading time, font-size control, print mode, markdown-it)
- [ ] **M10.2** — PDF viewer (`pdfjs-dist` lazy loaded, page nav, zoom, text selection, page 1 thumbnail preview)
- [ ] **M10.3** — Map renderer (Leaflet + OpenStreetMap lazy loaded, pins for all places, cluster > 10, fullscreen)
- [ ] **M10.4** — Recipe renderer (new `recipe` content type, ingredients checklist, numbered steps, scale slider)
- [ ] **M10.5** — Event renderer (new `event` content type, countdown, ICS download, Google Calendar URL)
- [ ] **M10.6** — Math renderer (KaTeX lazy loaded, `$...$` inline + `$$...$$` block, batch render on stream end)
- [ ] **M10.7** — Mermaid diagram renderer (lazy loaded, dark theme, flowchart/sequence/gantt, pan/zoom mobile)
- [ ] **M10.8** — Audio waveform player (WaveSurfer.js lazy loaded, orange waveform, click-to-seek)
- [ ] **M10.9** — Table renderer (sortable columns, row filter, CSV export, virtualise > 500 rows)
- [ ] **M10.10** — Timeline renderer (new `timeline` content type, vertical layout, animate in during streaming)
- [ ] **M10.11** — Code runner (HTML/CSS/JS sandboxed iframe, `sandbox="allow-scripts"`, output console)
- [ ] **M10.12** — Video renderer (native `<video>`, HLS.js lazy loaded, YouTube nocookie embed fallback)
- [ ] **TEST:M10** — Run `pnpm test` and `pnpm typecheck && pnpm lint`. Fix all failures before proceeding.
- [x] **M10.1** — Full article renderer (TOC sidebar, reading time, font-size control, print mode, markdown-it)
- [x] **M10.2** — PDF viewer (`pdfjs-dist` lazy loaded, page nav, zoom, text selection, page 1 thumbnail preview)
- [x] **M10.3** — Map renderer (Leaflet + OpenStreetMap lazy loaded, pins for all places, cluster > 10, fullscreen)
- [x] **M10.4** — Recipe renderer (new `recipe` content type, ingredients checklist, numbered steps, scale slider)
- [x] **M10.5** — Event renderer (new `event` content type, countdown, ICS download, Google Calendar URL)
- [x] **M10.6** — Math renderer (KaTeX lazy loaded, `$...$` inline + `$$...$$` block, batch render on stream end)
- [x] **M10.7** — Mermaid diagram renderer (lazy loaded, dark theme, flowchart/sequence/gantt, pan/zoom mobile)
- [x] **M10.8** — Audio waveform player (WaveSurfer.js lazy loaded, orange waveform, click-to-seek)
- [x] **M10.9** — Table renderer (sortable columns, row filter, CSV export, virtualise > 500 rows)
- [x] **M10.10** — Timeline renderer (new `timeline` content type, vertical layout, animate in during streaming)
- [x] **M10.11** — Code runner (HTML/CSS/JS sandboxed iframe, `sandbox="allow-scripts"`, output console)
- [x] **M10.12** — Video renderer (native `<video>`, HLS.js lazy loaded, YouTube nocookie embed fallback)
- [x] **TEST:M10** — Run `pnpm test` and `pnpm typecheck && pnpm lint`. Fix all failures before proceeding.
## M11: Nostr Ecosystem
@@ -70,36 +70,30 @@ const iframeRef = ref<HTMLIFrameElement | null>(null)
const isHtml = computed(() => props.language === 'html')
const srcdoc = computed(() => {
if (isHtml.value) {
// For HTML, wrap with console capture
return `<!DOCTYPE html>
<html><head><style>body{background:#0a0a0a;color:#e0e0e0;font-family:system-ui;margin:8px}</style>
<script>
const _post = (type, args) => parent.postMessage({type:'code-runner-console',level:type,text:args.map(a=>typeof a==='object'?JSON.stringify(a):String(a)).join(' ')},'*');
console.log = (...a) => _post('log', a);
console.error = (...a) => _post('error', a);
window.onerror = (m) => _post('error', [m]);
<\/script></head><body>${props.code}</body></html>`
}
const srcdoc = computed(() => buildSrcdoc(props.language, props.code))
// For JS/CSS, wrap in HTML
if (props.language === 'javascript' || props.language === 'js') {
return `<!DOCTYPE html><html><head>
<script>
const _post = (type, args) => parent.postMessage({type:'code-runner-console',level:type,text:args.map(a=>typeof a==='object'?JSON.stringify(a):String(a)).join(' ')},'*');
console.log = (...a) => _post('log', a);
console.error = (...a) => _post('error', a);
window.onerror = (m) => _post('error', [m]);
<\/script></head><body><script>${props.code}<\/script></body></html>`
}
function buildSrcdoc(lang: string, code: string): string {
// Build script tags via concatenation to avoid parser issues
const sOpen = String.fromCharCode(60) + 'script>'
const sClose = String.fromCharCode(60) + '/script>'
const capture = sOpen
+ 'const _post=(t,a)=>parent.postMessage({type:"code-runner-console",level:t,text:a.map(x=>typeof x==="object"?JSON.stringify(x):String(x)).join(" ")},"*");'
+ 'console.log=(...a)=>_post("log",a);'
+ 'console.error=(...a)=>_post("error",a);'
+ 'window.onerror=(m)=>_post("error",[m]);'
+ sClose
if (props.language === 'css') {
return `<!DOCTYPE html><html><head><style>${props.code}</style></head><body style="background:#0a0a0a"><div style="padding:16px;color:#e0e0e0;font-family:system-ui">CSS Preview</div></body></html>`
if (lang === 'html') {
return '<!DOCTYPE html><html><head><style>body{background:#0a0a0a;color:#e0e0e0;font-family:system-ui;margin:8px}</style>' + capture + '</head><body>' + code + '</body></html>'
}
if (lang === 'javascript' || lang === 'js') {
return '<!DOCTYPE html><html><head>' + capture + '</head><body>' + sOpen + code + sClose + '</body></html>'
}
if (lang === 'css') {
return '<!DOCTYPE html><html><head><style>' + code + '</style></head><body style="background:#0a0a0a"><div style="padding:16px;color:#e0e0e0;font-family:system-ui">CSS Preview</div></body></html>'
}
return ''
})
}
function runCode() {
consoleOutput.value = []