blob: ebdb794630facada9675f9356636abe5d41d3e27 [file]
/**
* Markdown rendering layer — eager entry.
*
* This module is intentionally lightweight: it only owns the
* `MakaUriContext` (which the renderer installs once at the App root)
* and a thin `Markdown` wrapper that `React.lazy`-loads the heavy
* streaming Markdown pipeline from `./markdown-body.js` on first use.
*
* Why the split: the markdown pipeline is by far the heaviest thing the
* chat shell transitively imports, yet it's only needed once a message
* actually renders. On a fresh launch (no active session) nothing ever
* mounts `<Markdown>`, so forcing the browser to parse hundreds of KB
* of highlight.js grammars before first paint was pure overhead. With
* the lazy split, that code is parsed on demand the first time a
* message appears, and cached for every subsequent render.
*
* The trust-boundary contract (URI allowlist, safe-scheme external
* gate, broken-link inline errors) lives in `markdown-body.tsx`
* alongside the `Markdown` body; see that file for the routing rationale.
*
* PR-UI-LIB-EXTRACT-6 (WAWQAQ msg `510fef52`, round 7/10): pulled out
* of `components.tsx`. `MakaUriContext` was already a public export
* (the renderer's main.tsx provides the dispatcher), so `index.ts`
* re-exports the new module to keep the `@maka/ui` surface identical.
* `Markdown` / `MarkdownLink` / `CodeBlock` and the helper functions
* remain package-private — only consumed within `@maka/ui`.
*/
import { createContext, lazy, Suspense, type ReactNode } from 'react';
// Heavy pipeline — parsed on first `<Markdown>` mount, not at app boot.
const MarkdownBody = lazy(() => import('./markdown-body.js').then((m) => ({ default: m.MarkdownBody })));
export function Markdown(props: { text: string; streaming?: boolean }) {
return (
<Suspense
// Plain-text fallback so message content is visible immediately while
// the markdown pipeline chunk finishes loading (a few tens of ms on a
// local file:// load; once cached, subsequent mounts are synchronous).
fallback={
<div className="maka-markdown maka-markdown-pending" style={{ whiteSpace: 'pre-wrap' }}>
{props.text}
</div>
}
>
<MarkdownBody text={props.text} streaming={props.streaming} />
</Suspense>
);
}
/**
* PR-UI-RENDER-2 — context for the internal-link dispatcher.
*
* The desktop renderer installs the dispatcher once at the App root
* (see `apps/desktop/src/renderer/main.tsx`). The dispatcher takes a
* typed `MakaUriDest` and routes to whatever real navigation surface
* the app uses (e.g. `setNavSelection({section: 'settings', tab: ...})`
* for `kind: 'settings'`, or `composer.prefill(text)` for `kind:
* 'compose'`). The Markdown link renderer never invokes navigation
* directly — that's the dispatcher's job, and the dispatcher is the
* single chokepoint to add observability / consent prompts later.
*
* Defined here (eager) rather than in `markdown-body.tsx` (lazy) so the
* context identity is stable across the eager/lazy boundary — the
* renderer installs the provider against THIS module's export, and the
* lazy body reads it via `useContext(MakaUriContext)` imported back
* from here.
*/
export const MakaUriContext = createContext<((dest: import('./maka-uri.js').MakaUriDest) => void) | undefined>(undefined);