@av-pi-studio/web-client 0.0.66 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/web/assets/{BinaryFallbackViewer-CID9YLTN.js → BinaryFallbackViewer-CcuvrutG.js} +2 -2
  3. package/dist/web/assets/{BinaryFallbackViewer-CID9YLTN.js.map → BinaryFallbackViewer-CcuvrutG.js.map} +1 -1
  4. package/dist/web/assets/ChatPanel-DgCKLvEc.js +3 -0
  5. package/dist/web/assets/ChatPanel-DgCKLvEc.js.map +1 -0
  6. package/dist/web/assets/{CodeView-5z34CDuP.js → CodeView-CXE9vlF1.js} +2 -2
  7. package/dist/web/assets/{CodeView-5z34CDuP.js.map → CodeView-CXE9vlF1.js.map} +1 -1
  8. package/dist/web/assets/{FilePanel-Ck53TDKW.js → FilePanel-jN6FYiGo.js} +2 -2
  9. package/dist/web/assets/{FilePanel-Ck53TDKW.js.map → FilePanel-jN6FYiGo.js.map} +1 -1
  10. package/dist/web/assets/{ImageViewer-Cd0XzsH0.js → ImageViewer-DdvsjbtG.js} +2 -2
  11. package/dist/web/assets/{ImageViewer-Cd0XzsH0.js.map → ImageViewer-DdvsjbtG.js.map} +1 -1
  12. package/dist/web/assets/{MarkdownFileViewer-C2QKqSk4.js → MarkdownFileViewer-BNG2in_C.js} +2 -2
  13. package/dist/web/assets/{MarkdownFileViewer-C2QKqSk4.js.map → MarkdownFileViewer-BNG2in_C.js.map} +1 -1
  14. package/dist/web/assets/{MoleculeViewerPanel-v4ED-TPp.js → MoleculeViewerPanel-CQQ9CIsD.js} +2 -2
  15. package/dist/web/assets/{MoleculeViewerPanel-v4ED-TPp.js.map → MoleculeViewerPanel-CQQ9CIsD.js.map} +1 -1
  16. package/dist/web/assets/{TerminalPanel-CrhMXlZQ.js → TerminalPanel-zJX45luf.js} +2 -2
  17. package/dist/web/assets/{TerminalPanel-CrhMXlZQ.js.map → TerminalPanel-zJX45luf.js.map} +1 -1
  18. package/dist/web/assets/{TextViewer-DXqtZond.js → TextViewer-DLce2RZp.js} +2 -2
  19. package/dist/web/assets/{TextViewer-DXqtZond.js.map → TextViewer-DLce2RZp.js.map} +1 -1
  20. package/dist/web/assets/{VideoViewer-tI0bhkFe.js → VideoViewer-I4xBhKGc.js} +2 -2
  21. package/dist/web/assets/{VideoViewer-tI0bhkFe.js.map → VideoViewer-I4xBhKGc.js.map} +1 -1
  22. package/dist/web/assets/index-KQwi4ZXj.js +18 -0
  23. package/dist/web/assets/index-KQwi4ZXj.js.map +1 -0
  24. package/dist/web/assets/markdown-CfR91Pqp.js +2 -0
  25. package/dist/web/assets/markdown-CfR91Pqp.js.map +1 -0
  26. package/dist/web/assets/{use-file-download-DAb889OO.js → use-file-download-Dv5HRkkB.js} +2 -2
  27. package/dist/web/assets/{use-file-download-DAb889OO.js.map → use-file-download-Dv5HRkkB.js.map} +1 -1
  28. package/dist/web/assets/{use-file-watch-cbUV4OS4.js → use-file-watch-DTOtoP8W.js} +2 -2
  29. package/dist/web/assets/{use-file-watch-cbUV4OS4.js.map → use-file-watch-DTOtoP8W.js.map} +1 -1
  30. package/dist/web/assets/{vendor-molviewer-CARaM-YQ.js → vendor-molviewer-DE5Dt5UM.js} +137 -137
  31. package/dist/web/assets/{vendor-molviewer-CARaM-YQ.js.map → vendor-molviewer-DE5Dt5UM.js.map} +1 -1
  32. package/dist/web/index.html +1 -1
  33. package/package.json +5 -5
  34. package/dist/web/assets/ChatPanel-BlQXKvbg.js +0 -3
  35. package/dist/web/assets/ChatPanel-BlQXKvbg.js.map +0 -1
  36. package/dist/web/assets/index-DCTqsrDx.js +0 -18
  37. package/dist/web/assets/index-DCTqsrDx.js.map +0 -1
  38. package/dist/web/assets/markdown-YhzfNBqU.js +0 -2
  39. package/dist/web/assets/markdown-YhzfNBqU.js.map +0 -1
@@ -1,2 +0,0 @@
1
- import{r as d,j as s}from"./vendor-react-CWo7Do4s.js";import{c as x}from"./vendor-highlight-Efc6A-g6.js";import{n as b,c as I,d as j,a as f,k as _,l as w,m as v,b as S,i as E,S as T,o as M}from"./index-DCTqsrDx.js";import{M as C,r as y}from"./vendor-markdown-YvkQLm_s.js";const k="github-dark";let m=null;const g=new Set(["plaintext"]);function H(){return m||(m=x({themes:[k],langs:["plaintext"]})),m}async function N(i,e){if(g.has(e))return e;try{return await i.loadLanguage(e),g.add(e),e}catch{return"plaintext"}}async function F(i,e){const t=await H(),n=await N(t,e);return t.codeToHtml(i,{lang:n,theme:k})}const L="_prose_1cnx5_1",U="_inlineCode_1cnx5_40",z="_inlineImage_1cnx5_66",R="_inlineImageSkeleton_1cnx5_75",P="_inlineImageFallback_1cnx5_86",c={prose:L,inlineCode:U,inlineImage:z,inlineImageSkeleton:R,inlineImageFallback:P},W=new Set(["http:","https:","data:","blob:"]),$=/^([a-zA-Z][a-zA-Z0-9+.-]*):/;function A(i,e,t){const n=i.trim();if(!n)return{kind:"unresolvable"};const a=n.split(/[?#]/,1)[0];if(!a)return{kind:"unresolvable"};const l=$.exec(a);if(l){const u=`${l[1].toLowerCase()}:`;return W.has(u)?{kind:"remote"}:{kind:"unresolvable"}}let o;return a.startsWith("/")?o=a:a==="~"||a.startsWith("~/")?o=t?b(a,t):null:o=I(a,e??""),o?j(o)!=="image"?{kind:"unresolvable"}:{kind:"local",path:o}:{kind:"unresolvable"}}function V(i,e,t,n){return i.kind==="remote"?{kind:"remote",src:t||"",alt:n}:i.kind==="unresolvable"?{kind:"unresolvable",fallbackText:n||t||"(image)"}:e.status==="loading"||e.status==="idle"?{kind:"loading"}:e.status==="ready"?{kind:"ready",objectUrl:e.objectUrl,alt:n,path:i.path}:{kind:"error",fallbackText:n||i.path||"(image)"}}function Z(i,e,t){if(!i||!e){t({status:"idle"});return}let n=!1;return t({status:"loading"}),_(i,e).then(a=>{n||t({status:"ready",objectUrl:a.objectUrl})}).catch(a=>{n||t({status:"error",message:a instanceof Error?a.message:String(a)})}),()=>{n=!0,w(i)}}function q(i){const e=f(l=>l.client),t=f(l=>l.daemon),[n,a]=d.useState({status:"idle"});return d.useEffect(()=>Z(i,e&&t?o=>v(t).download(o):null,a),[e,t,i]),n}const B=d.memo(function({src:e,alt:t,assetBase:n}){const a=S(),l=E(p=>p.open),o=d.useMemo(()=>A(e||"",n,a),[e,n,a]),u=q(o.kind==="local"?o.path:null),r=V(o,u,e,t);if(r.kind==="remote")return s.jsx("img",{src:r.src,alt:r.alt,className:c.inlineImage});if(r.kind==="unresolvable"||r.kind==="error")return s.jsx("code",{className:c.inlineImageFallback,children:r.fallbackText});if(r.kind==="loading")return s.jsx("div",{className:c.inlineImageSkeleton,children:s.jsx(T,{size:"sm"})});const h=()=>{l({id:M.file(r.path),kind:"file",label:r.path.split("/").pop()||r.path,closable:!0,data:{path:r.path},workspaceCwd:n||"~"})};return s.jsx("img",{src:r.objectUrl,alt:r.alt,className:c.inlineImage,title:r.path,onClick:h,style:{cursor:"pointer"}})});function D({language:i,code:e}){const[t,n]=d.useState(null);return d.useEffect(()=>{let a=!1;return F(e,i).then(l=>{a||n(l)}),()=>{a=!0}},[e,i]),t?s.jsx("div",{className:c.prose,dangerouslySetInnerHTML:{__html:t}}):s.jsx("pre",{children:s.jsx("code",{children:e})})}function G({className:i,children:e,...t}){const n=/language-(\w+)/.exec(i??""),a=!!n,l=String(e).replace(/\n$/,"");return a&&n?s.jsx(D,{language:n[1]??"plaintext",code:l}):s.jsx("code",{className:c.inlineCode,...t,children:e})}const X=d.memo(function({text:e,assetBase:t=null}){return s.jsx("div",{className:c.prose,children:s.jsx(C,{remarkPlugins:[y],components:{code:G,img:n=>s.jsx(B,{...n,assetBase:t})},children:e})})});export{X as M};
2
- //# sourceMappingURL=markdown-YhzfNBqU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"markdown-YhzfNBqU.js","sources":["../../../src/timeline/highlight.ts","../../../src/timeline/image-src.ts","../../../src/timeline/inline-image-view.ts","../../../src/hooks/use-inline-image.ts","../../../src/timeline/InlineImage.tsx","../../../src/timeline/markdown.tsx"],"sourcesContent":["/**\n * Shiki wrapper + lang-from-path map. Replaces the POC's CDN `highlight.js`\n * (POC_TO_APP_PLAN_UI.md §2, §4.8). Grammars/themes are loaded lazily and cached — the\n * highlighter is not paid for until a code block/file view actually renders.\n */\n\nimport { createHighlighter, type Highlighter } from \"shiki\";\n\n/** POC `getLangFromPath` extension → language map, ported verbatim. */\nconst EXT_TO_LANG: Record<string, string> = {\n js: \"javascript\",\n ts: \"typescript\",\n jsx: \"javascript\",\n tsx: \"typescript\",\n py: \"python\",\n rb: \"ruby\",\n rs: \"rust\",\n go: \"go\",\n java: \"java\",\n c: \"c\",\n cpp: \"cpp\",\n h: \"c\",\n hpp: \"cpp\",\n cs: \"csharp\",\n html: \"html\",\n css: \"css\",\n scss: \"scss\",\n json: \"json\",\n yaml: \"yaml\",\n yml: \"yaml\",\n toml: \"toml\",\n xml: \"xml\",\n md: \"markdown\",\n sh: \"bash\",\n bash: \"bash\",\n sql: \"sql\",\n swift: \"swift\",\n kt: \"kotlin\",\n dart: \"dart\",\n vue: \"xml\",\n svelte: \"xml\",\n};\n\nexport function langFromPath(path: string): string {\n const ext = path.split(\".\").pop()?.toLowerCase() ?? \"\";\n return EXT_TO_LANG[ext] ?? \"plaintext\";\n}\n\nconst THEME = \"github-dark\";\n\nlet highlighterPromise: Promise<Highlighter> | null = null;\nconst loadedLangs = new Set<string>([\"plaintext\"]);\n\nfunction getHighlighter(): Promise<Highlighter> {\n if (!highlighterPromise) {\n highlighterPromise = createHighlighter({ themes: [THEME], langs: [\"plaintext\"] });\n }\n return highlighterPromise;\n}\n\nasync function ensureLang(highlighter: Highlighter, lang: string): Promise<string> {\n if (loadedLangs.has(lang)) return lang;\n try {\n await highlighter.loadLanguage(lang as never);\n loadedLangs.add(lang);\n return lang;\n } catch {\n return \"plaintext\"; // unknown grammar to Shiki — fall back rather than throw\n }\n}\n\n/** Highlight `code` for `lang` (or a path via `langFromPath`) to sanitized HTML. */\nexport async function highlightToHtml(code: string, lang: string): Promise<string> {\n const highlighter = await getHighlighter();\n const resolved = await ensureLang(highlighter, lang);\n return highlighter.codeToHtml(code, { lang: resolved, theme: THEME });\n}\n","/**\n * Markdown image source classification (features/inline-image-rendering.md § Public Contract →\n * Markdown image source classification, § Asset base). The single gate every `![](src)` in a\n * rendered chat message passes through before any network request is even considered — deciding\n * remote (render directly), local (fetch via the daemon's file-transfer path, task-003), or\n * unresolvable (render as plain fallback text, never attempt a request).\n *\n * Pure: no store/hook/network imports, so this file needs no React or client mocks to test.\n */\n\nimport { detectViewerKind } from \"@pi-studio-ui/features/files/viewer-registry.js\";\nimport { normalizeCwd } from \"@pi-studio-ui/features/sessions/workspace-grouping.js\";\nimport { resolveWorkspacePath } from \"@pi-studio-ui/lib/paths.js\";\n\nexport type ImageSrcClassification =\n | { kind: \"remote\" }\n | { kind: \"local\"; path: string }\n | { kind: \"unresolvable\" };\n\nconst REMOTE_SCHEMES = new Set([\"http:\", \"https:\", \"data:\", \"blob:\"]);\n\n/** Matches a leading `scheme:` (e.g. `http:`, `file:`, `mailto:`) — not a Windows drive letter\n * (`C:\\...`) or a bare `:` with no scheme name. */\nconst SCHEME_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;\n\n/**\n * Classifies a markdown image `src`. `base` is the asset base (absolute, tilde-normalized) or\n * `null` when unknown; `homeDir` may be `null` when the daemon's home directory hasn't been\n * learned yet. Order matters — this mirrors the scope's classification table exactly:\n *\n * 1. empty/whitespace → unresolvable\n * 2. `http:`/`https:`/`data:`/`blob:` → remote\n * 3. any other `scheme:` prefix (including `file:`) → unresolvable\n * 4. `/…` → candidate local path as-is\n * 5. `~`/`~/…` → expand against `homeDir` (unknown home dir → unresolvable)\n * 6. `./…`, `../…`, bare relative → resolve against `base` (no base → unresolvable)\n * 7. final gate: the candidate must be an image per `detectViewerKind`, else unresolvable\n */\nexport function classifyImageSrc(\n src: string,\n base: string | null,\n homeDir: string | null,\n): ImageSrcClassification {\n const trimmed = src.trim();\n if (!trimmed) return { kind: \"unresolvable\" };\n\n // Markdown may carry a title/version via `#fragment`/`?query`; strip both before classifying so\n // a trailing `?` never defeats the extension gate below.\n const stripped = trimmed.split(/[?#]/, 1)[0]!;\n if (!stripped) return { kind: \"unresolvable\" };\n\n const schemeMatch = SCHEME_RE.exec(stripped);\n if (schemeMatch) {\n const scheme = `${schemeMatch[1]!.toLowerCase()}:`;\n return REMOTE_SCHEMES.has(scheme) ? { kind: \"remote\" } : { kind: \"unresolvable\" };\n }\n\n let candidate: string | null;\n if (stripped.startsWith(\"/\")) {\n candidate = stripped;\n } else if (stripped === \"~\" || stripped.startsWith(\"~/\")) {\n candidate = homeDir ? normalizeCwd(stripped, homeDir) : null;\n } else {\n candidate = resolveWorkspacePath(stripped, base ?? \"\");\n }\n\n if (!candidate) return { kind: \"unresolvable\" };\n if (detectViewerKind(candidate) !== \"image\") return { kind: \"unresolvable\" };\n return { kind: \"local\", path: candidate };\n}\n","/**\n * Pure, testable logic for rendering inline images — extracted from the component\n * so tests can verify branch logic without JSX rendering (mirror of molecule-reload.ts,\n * text-viewer-state.ts; see AGENTS.md testing conventions).\n *\n * Returns a discriminated union describing what to render: the component switches\n * on it to decide remote passthrough, skeleton, ready image, or text fallback.\n */\n\nimport type { ImageSrcClassification } from \"./image-src.js\";\nimport type { InlineImageState } from \"@pi-studio-ui/hooks/use-inline-image.js\";\n\nexport type InlineImageView =\n | { kind: \"remote\"; src: string; alt?: string }\n | { kind: \"unresolvable\"; fallbackText: string }\n | { kind: \"loading\" }\n | { kind: \"ready\"; objectUrl: string; alt?: string; path: string }\n | { kind: \"error\"; fallbackText: string };\n\n/**\n * Decides what to render for an inline image given its classification and loading state.\n * Pure: no hooks, no store access — all state passed in as arguments.\n */\nexport function selectInlineImageView(\n classification: ImageSrcClassification,\n imageState: InlineImageState,\n src: string | undefined,\n alt: string | undefined,\n): InlineImageView {\n if (classification.kind === \"remote\") {\n return { kind: \"remote\", src: src || \"\", alt };\n }\n\n if (classification.kind === \"unresolvable\") {\n const fallbackText = alt || src || \"(image)\";\n return { kind: \"unresolvable\", fallbackText };\n }\n\n // classification.kind === \"local\"\n if (imageState.status === \"loading\" || imageState.status === \"idle\") {\n return { kind: \"loading\" };\n }\n\n if (imageState.status === \"ready\") {\n return { kind: \"ready\", objectUrl: imageState.objectUrl, alt, path: classification.path };\n }\n\n // imageState.status === \"error\"\n const fallbackText = alt || classification.path || \"(image)\";\n return { kind: \"error\", fallbackText };\n}\n","/**\n * `use-inline-image` — fetches (via the shared ref-counted `inline-image-cache`) and hands back\n * an object URL for an inline chat image (features/inline-image-rendering.md § Inline image fetch\n * + cache). Consumed by the markdown `img` node override (task-004) once a `classifyImageSrc`\n * result is `local`.\n *\n * Deliberately NOT a TanStack Query hook, and therefore no `rpcKeys` entry: the whole point of\n * `inline-image-cache.ts` is a retention policy (revoke only on LRU eviction, never on unmount)\n * that Query's cache cannot express. Do not \"fix\" this into a `useQuery` — see that module's\n * header for why.\n *\n * `loadInlineImage` is the actual effect body, extracted for direct unit testing without\n * rendering — this package has no jsdom/React-Testing-Library environment (`AGENTS.md`'s testing\n * convention; mirrors `use-file-watch.ts`'s `watchFile` extraction).\n */\n\nimport { useEffect, useState } from \"react\";\nimport { useConnectionStore } from \"@pi-studio-ui/lib/connection/connection-store.js\";\nimport { acquireInlineImage, releaseInlineImage } from \"@pi-studio-ui/lib/inline-image-cache.js\";\nimport { transferFor } from \"./file-transfer-instance.js\";\n\nexport type InlineImageState =\n | { status: \"idle\" } // no client yet, or path is null\n | { status: \"loading\" }\n | { status: \"ready\"; objectUrl: string }\n | { status: \"error\"; message: string };\n\n/**\n * Runs one effect cycle: `download === null` (no live connection, or no path) is the idle case —\n * nothing to acquire, nothing to clean up. Otherwise acquires `path`, reports the loading →\n * ready/error transition via `onStateChange`, and returns a cleanup that releases the path and\n * ignores a resolution that lands after it (the `cancelled` flag pattern used across the existing\n * hooks, e.g. `use-checkout-status.ts`).\n */\nexport function loadInlineImage(\n path: string | null,\n download: ((path: string) => Promise<{ bytes: Uint8Array; mimeType?: string }>) | null,\n onStateChange: (state: InlineImageState) => void,\n): (() => void) | void {\n if (!path || !download) {\n onStateChange({ status: \"idle\" });\n return;\n }\n\n let cancelled = false;\n onStateChange({ status: \"loading\" });\n\n acquireInlineImage(path, download)\n .then((entry) => {\n if (!cancelled) onStateChange({ status: \"ready\", objectUrl: entry.objectUrl });\n })\n .catch((error: unknown) => {\n if (!cancelled) {\n onStateChange({\n status: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n });\n\n return () => {\n cancelled = true;\n releaseInlineImage(path);\n };\n}\n\nexport function useInlineImage(path: string | null): InlineImageState {\n const client = useConnectionStore((s) => s.client);\n const daemon = useConnectionStore((s) => s.daemon);\n const [state, setState] = useState<InlineImageState>({ status: \"idle\" });\n\n useEffect(() => {\n const download = client && daemon ? (p: string) => transferFor(daemon).download(p) : null;\n return loadInlineImage(path, download, setState);\n }, [client, daemon, path]);\n\n return state;\n}\n","/**\n * `![alt](src)` renderer for finalized assistant markdown (features/inline-image-rendering.md §\n * Behavior & Algorithms → Render pipeline). Handles remote passthrough, local file fetch via\n * useInlineImage (task-003), classification (task-002), and click-to-open file tab.\n *\n * Props are optional (react-markdown may omit them) and must be handled defensively.\n */\n\nimport { memo, useMemo } from \"react\";\nimport { classifyImageSrc } from \"./image-src.js\";\nimport { selectInlineImageView } from \"./inline-image-view.js\";\nimport { useInlineImage } from \"@pi-studio-ui/hooks/use-inline-image.js\";\nimport { useHomeDir } from \"@pi-studio-ui/hooks/use-home-dir.js\";\nimport { useTabStore, tabIds } from \"@pi-studio-ui/stores/tab-store.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport styles from \"./markdown.module.css\";\n\nexport interface InlineImageProps {\n src?: string;\n alt?: string;\n assetBase: string | null;\n}\n\nexport const InlineImage = memo(function InlineImage({ src, alt, assetBase }: InlineImageProps) {\n const homeDir = useHomeDir();\n const openTab = useTabStore((s) => s.open);\n\n // Classify the source once at render time.\n const classification = useMemo(\n () => classifyImageSrc(src || \"\", assetBase, homeDir),\n [src, assetBase, homeDir],\n );\n\n // For local images, fetch and track state.\n const imageState = useInlineImage(classification.kind === \"local\" ? classification.path : null);\n\n // Decide what to render.\n const view = selectInlineImageView(classification, imageState, src, alt);\n\n // Remote images: pass through unchanged (what react-markdown's default would do).\n if (view.kind === \"remote\") {\n return <img src={view.src} alt={view.alt} className={styles.inlineImage} />;\n }\n\n // Unresolvable or error: render text fallback in inline monospace.\n if (view.kind === \"unresolvable\" || view.kind === \"error\") {\n return <code className={styles.inlineImageFallback}>{view.fallbackText}</code>;\n }\n\n // Loading or idle: skeleton.\n if (view.kind === \"loading\") {\n return (\n <div className={styles.inlineImageSkeleton}>\n <Spinner size=\"sm\" />\n </div>\n );\n }\n\n // Ready: render the fetched image with click-to-open handler.\n const handleClick = () => {\n openTab({\n id: tabIds.file(view.path),\n kind: \"file\",\n label: view.path.split(\"/\").pop() || view.path,\n closable: true,\n data: { path: view.path },\n workspaceCwd: assetBase || \"~\",\n });\n };\n\n return (\n <img\n src={view.objectUrl}\n alt={view.alt}\n className={styles.inlineImage}\n title={view.path}\n onClick={handleClick}\n style={{ cursor: \"pointer\" }}\n />\n );\n});\n","/**\n * react-markdown + remark-gfm rendering, replacing the POC's CDN `marked` + `innerHTML`\n * (POC_TO_APP_PLAN_UI.md §4.8). Sanitized by default — react-markdown never injects raw HTML.\n * Fenced code blocks highlight asynchronously via Shiki (`highlight.ts`).\n */\n\nimport { memo, useEffect, useState, type ComponentPropsWithoutRef } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { highlightToHtml } from \"./highlight.js\";\nimport styles from \"./markdown.module.css\";\nimport { InlineImage } from \"./InlineImage.js\";\n\ninterface CodeBlockProps {\n language: string;\n code: string;\n}\n\nfunction CodeBlock({ language, code }: CodeBlockProps) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void highlightToHtml(code, language).then((result) => {\n if (!cancelled) setHtml(result);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n if (html) {\n // Shiki's output is generated HTML from a trusted local highlighter (no user HTML is ever\n // injected) — the only risk is `code`, which Shiki escapes internally before tokenizing.\n return <div className={styles.prose} dangerouslySetInnerHTML={{ __html: html }} />;\n }\n return (\n <pre>\n <code>{code}</code>\n </pre>\n );\n}\n\ntype MarkdownCodeProps = ComponentPropsWithoutRef<\"code\"> & { className?: string };\n\nfunction CodeRenderer({ className, children, ...rest }: MarkdownCodeProps) {\n const match = /language-(\\w+)/.exec(className ?? \"\");\n const isBlock = Boolean(match);\n const code = String(children).replace(/\\n$/, \"\");\n if (isBlock && match) {\n return <CodeBlock language={match[1] ?? \"plaintext\"} code={code} />;\n }\n return (\n <code className={styles.inlineCode} {...rest}>\n {children}\n </code>\n );\n}\n\nexport interface MarkdownProps {\n text: string;\n assetBase?: string | null;\n}\n\n/** Renders assistant/tool markdown text. Memoized — text is immutable once a turn finalizes. */\nexport const Markdown = memo(function Markdown({ text, assetBase = null }: MarkdownProps) {\n return (\n <div className={styles.prose}>\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n code: CodeRenderer,\n img: (props) => <InlineImage {...props} assetBase={assetBase} />,\n }}\n >\n {text}\n </ReactMarkdown>\n </div>\n );\n});\n"],"names":["THEME","highlighterPromise","loadedLangs","getHighlighter","createHighlighter","ensureLang","highlighter","lang","highlightToHtml","code","resolved","REMOTE_SCHEMES","SCHEME_RE","classifyImageSrc","src","base","homeDir","trimmed","stripped","schemeMatch","scheme","candidate","normalizeCwd","resolveWorkspacePath","detectViewerKind","selectInlineImageView","classification","imageState","alt","fallbackText","loadInlineImage","path","download","onStateChange","cancelled","acquireInlineImage","entry","error","releaseInlineImage","useInlineImage","client","useConnectionStore","s","daemon","state","setState","useState","useEffect","p","transferFor","InlineImage","memo","assetBase","useHomeDir","openTab","useTabStore","useMemo","view","jsx","styles","Spinner","handleClick","tabIds","CodeBlock","language","html","setHtml","result","CodeRenderer","className","children","rest","match","isBlock","Markdown","text","ReactMarkdown","remarkGfm","props"],"mappings":"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"}