fix(files): keep attachment markdown refs from resolving into the workspace, and render files without chat line breaks (#503 review)

- A markdown preview opened by attachment id under a bare file name
  (attachment cards, history drawer) no longer resolves relative refs
  against the workspace root: filePreviewText carries attachmentId, and
  the rebase pass turns those images into their alt text and unwraps
  those links. Absolute-path and workspace previews are unchanged.
- _renderMarkdown(text, { breaks = true } = {}): the File Viewer passes
  breaks: false, so a hard-wrapped paragraph renders as one paragraph;
  the Response Viewer keeps a <br> per newline.
- Absolute paths linkified inside a rendered document now carry the
  preview's data-session-id.
- CLAUDE.md, architecture-invariants and the Working-With-Files wiki page
  now say that only an in-workspace path clicked in the terminal keeps
  the tail viewer.
- Tests in test/file-preview-markdown.test.ts for all three fixes,
  including an end-to-end run of the shipping app.js + marked + DOMPurify.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-01 11:19:46 +02:00
parent 3af1ff6fae
commit 73c0bfccc4
6 changed files with 205 additions and 29 deletions
+8 -3
View File
@@ -2296,13 +2296,18 @@ class CodemanApp {
return processed.replace(/__CODEMAN_FENCE_(\d+)__/g, (_m, i) => placeholders[Number(i)]);
}
/** Render markdown to sanitized HTML, falling back to plain text if marked.js unavailable */
_renderMarkdown(text) {
/**
* Render markdown to sanitized HTML, falling back to plain text if marked.js unavailable.
* `breaks` turns every source newline into a <br>: right for chat, where a
* newline is the agent's line break, wrong for a file (the File Viewer passes
* false), where a README hard-wrapped at 80 columns would break at every wrap.
*/
_renderMarkdown(text, { breaks = true } = {}) {
const src = text || '';
if (typeof marked !== 'undefined' && marked.parse) {
try {
const prepared = this._preprocessAsciiArt(src);
let html = this._sanitizeHtml(marked.parse(prepared, { breaks: true, gfm: true }));
let html = this._sanitizeHtml(marked.parse(prepared, { breaks, gfm: true }));
// Wrap tables in a horizontal-scroll container so they overflow gracefully
// on mobile without collapsing into block-level cells.
html = html.replace(/<table>/g, '<div class="rv-table-wrap"><table>')