Build image / build-and-push (push) Successful in 7s
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]>
42 lines
1.7 KiB
TypeScript
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
|
|
}
|
|
})
|
|
}
|