Release YG (v0.51.677): render cron run output as literal pre/code (#4977)

Release YG (v0.51.677): render cron run output as literal pre/code (#4977)
This commit is contained in:
nesquena-hermes
2026-06-26 05:11:28 -07:00
committed by GitHub
3 changed files with 33 additions and 7 deletions
+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.677] — 2026-06-26 — Release YG (cron run logs render as literal text, not mangled markdown)
### Fixed
- **Cron job run logs (prompt/response in the cron detail panel) now render as literal preformatted text instead of being mangled by Markdown.** The expanded run body and the "View full output" view passed raw output through `renderMd()`, so JSON/plain-text logs lost their newlines, had special characters interpreted, and lines beginning with `#`/`|`/`>` turned into headings/tables/blockquotes. Both paths now render via a DOM-created `<pre><code>` with `textContent`, preserving whitespace exactly and rendering nothing as Markdown (also XSS-safe — no `innerHTML` on raw output). The usage footer and action button stay outside the preformatted block. Thanks @luandnh. (#4977)
## [v0.51.676] — 2026-06-26 — Release YF (submitted message no longer renders twice — or vanishes — on active reload)
### Fixed
+25 -7
View File
@@ -894,12 +894,17 @@ async function _loadRunContent(jobId, filename, runId){
const expanded = _cronExpansionGet(_cronRunExpandKey(jobId, filename));
const output = expanded ? (data.content || data.snippet || '') : (data.snippet || data.content || '');
body.classList.toggle('expanded', expanded);
// Render markdown content using the same renderer as chat messages
if (typeof renderMd === 'function') {
body.innerHTML = renderMd(output);
} else {
body.textContent = output;
}
// Cron run output is never authored Markdown — render as literal
// preformatted text using DOM-created <pre><code> so all content
// (including shapes starting with #, |, >, ``` and embedded fences)
// renders verbatim without Markdown interpretation.
body.innerHTML = '';
const pre = document.createElement('pre');
pre.className = 'cron-run-pre';
const code = document.createElement('code');
code.textContent = output;
pre.appendChild(code);
body.appendChild(pre);
const usageStrip = _formatCronRunUsageStrip(data.usage);
if (usageStrip) {
const usage = document.createElement('div');
@@ -915,7 +920,20 @@ async function _loadRunContent(jobId, filename, runId){
btn.onclick = () => {
_cronExpansionSet(_cronRunExpandKey(jobId, filename), true);
body.classList.add('expanded');
body.innerHTML = renderMd ? renderMd(data.content) : data.content;
body.innerHTML = '';
const pre = document.createElement('pre');
pre.className = 'cron-run-pre';
const code = document.createElement('code');
code.textContent = data.content || '';
pre.appendChild(code);
body.appendChild(pre);
const usageStrip = _formatCronRunUsageStrip(data.usage);
if (usageStrip) {
const usage = document.createElement('div');
usage.className = 'cron-run-usage-strip cron-run-usage-footer';
usage.textContent = usageStrip;
body.appendChild(usage);
}
btn.remove();
};
body.appendChild(btn);
+2
View File
@@ -5533,6 +5533,8 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.detail-run-body{display:none;margin-top:6px;font-size:12px;color:var(--muted);white-space:pre-wrap;line-height:1.5;max-height:260px;overflow-y:auto;background:var(--sidebar);border:1px solid var(--border);border-radius:6px;padding:8px 10px;}
.detail-run-item.open .detail-run-body{display:block;}
.detail-run-body.expanded{max-height:none;overflow-y:visible;}
.detail-run-body .cron-run-pre{margin:0;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;}
.detail-run-body .cron-run-pre code{background:transparent;padding:0;font-family:var(--font-mono);}
.workspace-panel-tabs{display:flex;gap:4px;padding:6px 8px;border-bottom:1px solid var(--border);}
.workspace-panel-tab{flex:1;border:1px solid transparent;background:transparent;color:var(--muted);border-radius:7px;padding:5px 8px;font-size:12px;cursor:pointer;}
.workspace-panel-tab.active{background:var(--surface-subtle);color:var(--text);border-color:var(--border2);}