Files
pansy/web/src/lib/lazyPage.ts
T
steve 08d8c5e47d
Build image / build-and-push (push) Successful in 7s
Render the assistant's replies as Markdown (#118)
Render assistant chat output as GFM Markdown (tables, lists, code, headings), lazy-loaded so the ~150 KB renderer only ships when an assistant message shows. Hardened per review: no <img> (exfiltration-beacon guard), no raw HTML, error-boundary + stale-chunk recovery around the lazy chunk, GFM column alignment, and memoized parsing.

Co-authored-by: Steve Dudenhoeffer <[email protected]>
2026-07-22 16:42:12 +00:00

42 lines
1.7 KiB
TypeScript

import { lazy, type ComponentType } from 'react'
/**
* Lazily load a component by its named export, with recovery for the stale-chunk
* problem. A push to main redeploys, so a still-open app references chunk hashes
* the server has just replaced; that import 404s and React.lazy MEMOIZES the
* rejection, so a "Try again" can never recover — the user is stuck until a manual
* hard reload. On the first such failure we reload once (fetching the fresh index
* + hashes); a session flag stops a reload loop, and a success clears it so a
* later genuine failure can reload again.
*
* Shared by the route splits (router.tsx) and any feature-level lazy load (the
* assistant's Markdown renderer), so they all get the same recovery.
*/
export function lazyPage<M, K extends keyof M>(load: () => Promise<M>, name: K) {
// Preserve the component's own prop type so callers keep type-checked props
// (e.g. MarkdownMessage's `children: string`), rather than erasing to `{}`.
type C = M[K] extends ComponentType<infer P> ? ComponentType<P> : never
return lazy<C>(async () => {
try {
const mod = await load()
try {
sessionStorage.removeItem('pansy:chunk-reload')
} catch {
/* storage unavailable — fine */
}
return { default: mod[name] as C }
} catch (err) {
try {
if (!sessionStorage.getItem('pansy:chunk-reload')) {
sessionStorage.setItem('pansy:chunk-reload', '1')
window.location.reload()
return await new Promise<{ default: C }>(() => {}) // hold for the reload
}
} catch {
/* storage unavailable — fall through to surface the error */
}
throw err // already reloaded once (or can't); let the error boundary show it
}
})
}