Files
hapi/web/e2e-fixtures/file-md-preview-fixture.tsx
T
a82dd49049 feat(web): markdown Source | Preview toggle in session file pane (#957)
* feat(web): markdown Source | Preview toggle in session file pane

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/hapi#954

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

* fix(web): cast standalone MarkdownRenderer components for react-markdown

Soup verify gate: defaultComponents merge type is wider than
react-markdown Components; standalone file-pane path needs explicit cast.

* fix(web): route file-pane markdown fences through SyntaxHighlighter

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/hapi#957.

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

* fix(web): detect fenced vs inline code in standalone markdown preview

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>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 11:06:38 +08:00

89 lines
3.1 KiB
TypeScript

/*
* Standalone Vite-served fixture for the file-pane markdown Source |
* Preview Playwright smoke. Mounts the same toggle + MarkdownRenderer
* path as `file.tsx` without the HAPI auth / git / socket stack.
*/
import React from 'react'
import ReactDOM from 'react-dom/client'
import '../src/index.css'
import { I18nProvider } from '../src/lib/i18n-context'
import { MarkdownRenderer } from '../src/components/MarkdownRenderer'
import {
getInitialMarkdownPreviewMode,
persistMarkdownPreviewMode,
type MarkdownPreviewMode,
} from '../src/lib/file-markdown-preview'
import { useTranslation } from '../src/lib/use-translation'
const SAMPLE_MARKDOWN = `# Teams and channels
| Channel | Purpose |
| --- | --- |
| general | Day-to-day coordination |
| incidents | Outage response |
\`\`\`ts
export const ok = true
\`\`\`
> Preview uses the same markdown pipeline as chat.
`
function FileMarkdownPreviewFixture() {
const { t } = useTranslation()
const [mode, setMode] = React.useState<MarkdownPreviewMode>(() => getInitialMarkdownPreviewMode())
const showSource = mode === 'source'
const setMarkdownPreviewMode = (next: MarkdownPreviewMode) => {
setMode(next)
persistMarkdownPreviewMode(next)
}
return (
<div data-testid="file-md-preview-fixture" className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<button
type="button"
data-testid="markdown-mode-source"
onClick={() => setMarkdownPreviewMode('source')}
className={`rounded px-3 py-1 text-xs font-semibold ${showSource ? 'bg-[var(--app-button)] text-[var(--app-button-text)] opacity-80' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
{t('file.page.tab.source')}
</button>
<button
type="button"
data-testid="markdown-mode-preview"
onClick={() => setMarkdownPreviewMode('preview')}
className={`rounded px-3 py-1 text-xs font-semibold ${!showSource ? 'bg-[var(--app-button)] text-[var(--app-button-text)] opacity-80' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
{t('file.page.tab.preview')}
</button>
</div>
{showSource ? (
<pre
data-testid="markdown-source-view"
className="overflow-auto rounded-md bg-[var(--app-code-bg)] p-3 text-xs font-mono"
>
<code>{SAMPLE_MARKDOWN}</code>
</pre>
) : (
<div data-testid="markdown-preview-view" className="markdown-content">
<MarkdownRenderer content={SAMPLE_MARKDOWN} standalone />
</div>
)}
</div>
)
}
const rootEl = document.getElementById('root')
if (rootEl) {
ReactDOM.createRoot(rootEl).render(
<React.StrictMode>
<I18nProvider>
<FileMarkdownPreviewFixture />
</I18nProvider>
</React.StrictMode>
)
}