Skip to content

feat(web): markdown Source | Preview toggle in session file pane#957

Open
heavygee wants to merge 4 commits into
tiann:mainfrom
heavygee:feat/file-markdown-preview
Open

feat(web): markdown Source | Preview toggle in session file pane#957
heavygee wants to merge 4 commits into
tiann:mainfrom
heavygee:feat/file-markdown-preview

Conversation

@heavygee

@heavygee heavygee commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a Source | Preview toggle on the session File tab for .md / .mdx files. Preview renders markdown via MarkdownRenderer in a new standalone mode (the file route sits outside the assistant-ui thread). Source keeps the existing Shiki-highlighted raw view. Default is Preview; the choice persists in localStorage (hapi.filePreview.markdownMode.v1).

Includes vitest coverage for the helper + route, a Playwright smoke spec, and standalone file-pane markdown rendering that routes fenced code blocks through the same SyntaxHighlighter / MARKDOWN_COMPONENTS_BY_LANGUAGE pipeline as chat (mermaid included).

Known v1 gaps (follow-up, not blockers):

  • Relative markdown links/images in the file pane are not resolved against the repo root.

Test plan

  • bun typecheck (cli + web + hub)
  • cd web && bun run test — 1114/1114 pass
  • Playwright e2e/file-md-preview.spec.ts (local smoke; not in upstream CI)
  • Manual: Files tab → markdown file → Preview ↔ Source ↔ reload persistence

Issues

Closes #954

heavygee and others added 2 commits June 19, 2026 15:31
Add Source | Preview toggle for .md/.mdx files in the session file route,
defaulting to preview with localStorage persistence. Reuse chat markdown
pipeline via MarkdownRenderer standalone mode (no assistant-ui thread).

Includes unit tests, Playwright smoke, and e2e fixture.

Closes tiann#954

Co-authored-by: Cursor <cursoragent@cursor.com>
Soup verify gate: defaultComponents merge type is wider than
react-markdown Components; standalone file-pane path needs explicit cast.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Markdown preview loses fenced code block rendering — the new standalone path calls ReactMarkdown directly with defaultComponents, but those components depend on the MarkdownTextPrimitive pre/code override to detect fenced code blocks and route them through SyntaxHighlighter / MARKDOWN_COMPONENTS_BY_LANGUAGE. In this branch, a fenced block renders as a normal <pre> containing inline-styled <code>, so markdown files with code fences or mermaid blocks lose the existing code shell, syntax highlighting, and diagram rendering. Evidence: web/src/components/MarkdownRenderer.tsx:34, triggered by web/src/routes/sessions/file.tsx:420.
    Suggested fix:
    export function MarkdownRenderer(props: MarkdownRendererProps) {
        return <MarkdownContent {...props} />
    }
    
    // file preview can use the same renderer path:
    <MarkdownRenderer content={decodedContent} />

Summary

  • Review mode: initial
  • One functional regression found in the markdown preview rendering path. The added tests cover headings/tables/source toggle, but not fenced code blocks or mermaid blocks, which is where the standalone renderer diverges from the production chat renderer.

Testing

  • Not run (automation)

HAPI Bot

Comment thread web/src/components/MarkdownRenderer.tsx
Standalone file preview now mirrors chat code-block rendering: fenced
blocks use SyntaxHighlighter and MARKDOWN_COMPONENTS_BY_LANGUAGE (mermaid
included) without requiring ThreadPrimitive context.

Addresses HAPI Bot Major on tiann#957.

Co-authored-by: Cursor <cursoragent@cursor.com>
@heavygee

Copy link
Copy Markdown
Collaborator Author

Addressed the Major finding in 2daee4f: standalone file-pane preview now routes fenced code blocks through SyntaxHighlighter and MARKDOWN_COMPONENTS_BY_LANGUAGE (same path as chat, without requiring ThreadPrimitive). Added MarkdownRenderer.test.tsx + e2e fixture assertion for a fenced block.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Inline markdown code renders as full code blocks - react-markdown@10.1.0 does not pass an inline boolean to custom code components; its public Components type is intrinsic element props plus node. Because this branch is never taken, inline backtick code in the file markdown preview falls through to the block renderer and gets a header plus .aui-md-codeblock with language unknown. Evidence: web/src/components/MarkdownRenderer.tsx:36.
    Suggested fix:
    import { Children, isValidElement, type ReactElement } from 'react'
    
    function StandaloneCode(props: ComponentPropsWithoutRef<'code'>) {
        const Code = defaultComponents.code!
        return <Code {...props} />
    }
    
    function StandalonePre(props: ComponentPropsWithoutRef<'pre'>) {
        const child = Children.toArray(props.children)[0]
        if (!isValidElement<ComponentPropsWithoutRef<'code'>>(child)) {
            const Pre = defaultComponents.pre!
            return <Pre {...props} />
        }
    
        const className = String(child.props.className ?? '')
        const language = /language-(\w+)/.exec(className)?.[1] ?? 'unknown'
        const code = String(child.props.children ?? '').replace(/\n$/, '')
        const Highlighter = MARKDOWN_COMPONENTS_BY_LANGUAGE[language as keyof typeof MARKDOWN_COMPONENTS_BY_LANGUAGE]?.SyntaxHighlighter ?? SyntaxHighlighter
        const CodeHeader = defaultComponents.CodeHeader as ComponentType<CodeHeaderProps>
        const Pre = defaultComponents.pre!
        const Code = defaultComponents.code!
    
        return (
            <>
                <CodeHeader language={language} code={code} />
                <Highlighter language={language} code={code} components={{ Pre, Code }} />
            </>
        )
    }
    
    const components = useMemo<Components>(() => ({
        ...(componentsRest as Components),
        pre: StandalonePre,
        code: StandaloneCode,
    }), [componentsRest])

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • One regression remains in the standalone markdown renderer. The added tests check fenced code blocks, but not inline code inside normal markdown prose.

Testing

  • Not run (automation; bun is unavailable in this runner)

HAPI Bot

Comment thread web/src/components/MarkdownRenderer.tsx Outdated
Move block detection to the pre override (react-markdown v10 does not pass
inline to custom code components). Add inline-code regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • No issues found.

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • The prior inline-code rendering finding appears addressed in the current head. I did not find any remaining correctness/security regression in the added markdown preview toggle or standalone renderer path.

Testing

  • Not run locally: bun is unavailable in this runner. Static review plus git diff --check completed.

HAPI Bot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): markdown Source | Preview toggle in session file pane

1 participant