feat(changes): markdown reading mode for .md op previews

The changes tab preview pane gains a Reading/Raw toggle for .md/.markdown/.mdx session ops (read, write, and edit alike). Reading mode renders the resulting document through the shared MarkdownText pass (the same stack as the editor preview): GFM tables with alignment, task lists, strikethrough, footnotes, math, and code fences, with local image destinations rewritten through the session-scoped /sidebar/file media route (rewriteLocalImageUrls). Content sources: read -> parseReadContent (envelope stripped, blank lines preserved — markdown block structure depends on them, which parseReadLines drops); write -> the written content; edit -> the reconstructed resulting document from the in-window prior content when known, else the new snippet. Ported from dsh-file-trace v0.2.0 (PR #471 lineage), re-based onto the unified changes tab (#475) and the repo shared-renderer conventions (markdownTextProps labels contract, skin tokens, 21-dictionary locale parity).
This commit is contained in:
lhh010
2026-09-01 18:23:28 +08:00
parent c1992125d0
commit b7138cf4fd
27 changed files with 195 additions and 6 deletions
+22 -1
View File
@@ -4,7 +4,7 @@
* (their full behavior suites live in the standalone dsh-file-trace plugin).
*/
import { describe, expect, it } from 'vitest'
import { extractFileOps, groupByFile, knownContentBefore, parseReadLines } from '../src/client/changes/ops.ts'
import { extractFileOps, groupByFile, knownContentBefore, parseReadContent, parseReadLines } from '../src/client/changes/ops.ts'
import { diffLines, buildDiffSegments, coalesceInline, diffInline } from '../src/client/diff/rows.ts'
import { langOfPath, scanLine } from '../src/client/diff/highlight.ts'
import type { SidebarSessionEvent } from '../src/context-types.ts'
@@ -29,6 +29,27 @@ function result(seq: number, callId: string, text: string, isError = false, time
})
}
describe('parseReadContent', () => {
it('strips the envelope and line-number prefixes but keeps blank lines', () => {
const raw = [
'<path>notes.md</path>',
'<type>file</type>',
'(Showing lines 1 to 4)',
'<content>',
'1: # Title',
'2: ',
'3: para one',
'4: ',
'</content>',
].join('\n')
expect(parseReadContent(raw)).toBe('# Title\n\npara one')
})
it('returns the raw text unchanged when no <content> section is present', () => {
expect(parseReadContent('plain text')).toBe('plain text')
})
})
describe('extractFileOps', () => {
it('seeds running ops from tool/call and settles them from tool/result', () => {
const ops = extractFileOps([