ai-or-die 0.1.85 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-or-die",
3
- "version": "0.1.85",
3
+ "version": "0.1.87",
4
4
  "description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -267,6 +267,91 @@ function safeJson(value) {
267
267
  return JSON.stringify(value).replace(/<\//g, '<\\/');
268
268
  }
269
269
 
270
+ function isMarkdownFile(file) {
271
+ const lower = String(file || '').toLowerCase();
272
+ return lower.endsWith('.md') || lower.endsWith('.markdown');
273
+ }
274
+
275
+ // Build a self-contained HTML shell that renders markdown SOURCE through the
276
+ // existing client renderer (window.markdownRender.renderInto). This is the
277
+ // fallback path for /view when an artifact is markdown rather than HTML — most
278
+ // plans arrive already-HTML, but a markdown plan must NEVER be shown as raw
279
+ // bytes. The shell is later passed through injectLavishSdk so the annotation
280
+ // SDK loads on top.
281
+ //
282
+ // Absolute script paths are load-bearing: injectLavishSdk inserts a
283
+ // <base href="/api/artifact/:id/asset/..."> and a path-relative
284
+ // "markdown-render.js" would resolve against that asset base (404). The renderer
285
+ // itself lazy-loads /vendor/marked.min.js + /vendor/purify.min.js with absolute
286
+ // paths too, so they survive the base. All three are served by the pre-auth
287
+ // express.static mount, so the sandboxed iframe loads them same-origin without a
288
+ // token.
289
+ function markdownArtifactShell(source, options) {
290
+ options = options || {};
291
+ const title = options.title ? String(options.title) : 'Markdown artifact';
292
+ const src = source == null ? '' : String(source);
293
+ return [
294
+ '<!doctype html>',
295
+ '<html lang="en">',
296
+ '<head>',
297
+ '<meta charset="utf-8">',
298
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
299
+ '<title>' + escapeAttr(title) + '</title>',
300
+ '<style>',
301
+ ':root{color-scheme:light}',
302
+ 'html,body{margin:0;padding:0;background:#fbfbfa;color:#1f2328}',
303
+ 'body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Serif",Georgia,serif;',
304
+ 'font-size:16px;line-height:1.65;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}',
305
+ '.md-reading-column{max-width:760px;margin:0 auto;padding:40px 28px 96px;box-sizing:border-box}',
306
+ '.fb-markdown-rendered,.fb-md-fallback{overflow-wrap:break-word;word-break:break-word}',
307
+ '.fb-markdown-rendered h1,.fb-markdown-rendered h2,.fb-markdown-rendered h3{line-height:1.25;font-weight:650;margin:1.6em 0 .6em}',
308
+ '.fb-markdown-rendered h1{font-size:1.9em;border-bottom:1px solid #e2e2df;padding-bottom:.3em}',
309
+ '.fb-markdown-rendered h2{font-size:1.45em;border-bottom:1px solid #e8e8e5;padding-bottom:.25em}',
310
+ '.fb-markdown-rendered h3{font-size:1.2em}',
311
+ '.fb-markdown-rendered p,.fb-markdown-rendered ul,.fb-markdown-rendered ol{margin:.7em 0}',
312
+ '.fb-markdown-rendered code,.fb-markdown-rendered pre{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace}',
313
+ '.fb-markdown-rendered code{background:#f1f1ef;border-radius:4px;padding:.15em .35em;font-size:.88em}',
314
+ '.fb-markdown-rendered pre{background:#f6f6f4;border:1px solid #e6e6e3;border-radius:8px;padding:14px 16px;overflow:auto;line-height:1.5}',
315
+ '.fb-markdown-rendered pre code{background:none;padding:0;font-size:.86em}',
316
+ '.fb-markdown-rendered blockquote{margin:.8em 0;padding:.1em 1em;border-left:3px solid #d7d7d3;color:#56595f}',
317
+ '.fb-markdown-rendered table{border-collapse:collapse;margin:1em 0;display:block;overflow:auto}',
318
+ '.fb-markdown-rendered th,.fb-markdown-rendered td{border:1px solid #e2e2df;padding:6px 12px}',
319
+ '.fb-markdown-rendered img{max-width:100%}',
320
+ '.fb-markdown-rendered a{color:#0b66c3}',
321
+ '.fb-md-loading{color:#8a8d92;font-size:14px}',
322
+ '.fb-md-feature-unavailable{color:#9a6b00;background:#fff7e0;border:1px solid #f0dca0;border-radius:6px;padding:8px 10px;font-size:13px;margin:0 0 10px}',
323
+ '.fb-md-fallback pre{white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;background:#f6f6f4;border:1px solid #e6e6e3;border-radius:8px;padding:14px 16px;margin:0}',
324
+ '</style>',
325
+ '</head>',
326
+ '<body>',
327
+ '<main class="md-reading-column"><div id="md-artifact-root" class="fb-md-loading">Rendering markdown...</div></main>',
328
+ '<script type="application/json" id="md-artifact-source">' + safeJson(src) + '</script>',
329
+ '<script src="/markdown-render.js"></script>',
330
+ '<script>(function(){',
331
+ 'function boot(){',
332
+ 'var root=document.getElementById("md-artifact-root");',
333
+ 'var raw=document.getElementById("md-artifact-source");',
334
+ 'var source="";try{source=JSON.parse(raw.textContent||\'""\');}catch(e){source=raw.textContent||"";}',
335
+ 'if(!root)return;',
336
+ 'if(window.markdownRender&&typeof window.markdownRender.renderInto==="function"){',
337
+ 'window.markdownRender.renderInto(root,source,{enableMermaid:true,enableKatex:true}).catch(function(){renderRaw(root,source);});',
338
+ '}else{renderRaw(root,source);}',
339
+ '}',
340
+ 'function renderRaw(root,source){',
341
+ 'while(root.firstChild)root.removeChild(root.firstChild);',
342
+ 'root.className="fb-md-fallback";',
343
+ 'var note=document.createElement("div");note.className="fb-md-feature-unavailable";',
344
+ 'note.textContent="Markdown renderer unavailable; showing source.";',
345
+ 'var pre=document.createElement("pre");pre.textContent=source;',
346
+ 'root.appendChild(note);root.appendChild(pre);',
347
+ '}',
348
+ 'if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",boot,{once:true});}else{boot();}',
349
+ '})();</script>',
350
+ '</body>',
351
+ '</html>',
352
+ ].join('\n');
353
+ }
354
+
270
355
  function injectLavishSdk(html, options) {
271
356
  options = options || {};
272
357
  const sessionId = options.sessionId;
@@ -574,8 +659,14 @@ function createArtifactReviewRouter(options) {
574
659
  try {
575
660
  const stat = fs.statSync(validation.path);
576
661
  if (!stat.isFile()) return res.status(400).json({ error: 'file must be a regular file' });
577
- html = fs.readFileSync(validation.path, 'utf8');
662
+ const raw = fs.readFileSync(validation.path, 'utf8');
578
663
  review.file = validation.path;
664
+ // Markdown FALLBACK: a .md/.markdown artifact is wrapped in a self-contained
665
+ // renderer shell (never shown as raw bytes). HTML and everything else keep
666
+ // the raw path. Both then flow through injectLavishSdk so annotation works.
667
+ html = isMarkdownFile(validation.path)
668
+ ? markdownArtifactShell(raw, { title: path.basename(validation.path) })
669
+ : raw;
579
670
  } catch (err) {
580
671
  if (err && err.code === 'ENOENT') return res.status(404).json({ error: 'file not found' });
581
672
  return res.status(500).json({ error: 'read failed', message: err.message });
@@ -834,5 +925,7 @@ module.exports = {
834
925
  createArtifactReviewRouter,
835
926
  feedbackHasData,
836
927
  injectLavishSdk,
928
+ isMarkdownFile,
929
+ markdownArtifactShell,
837
930
  resolveArtifactAsset,
838
931
  };