skillhub/web/index.html
Michael a063090a52
fix(web): stop React 19 portal removeChild/insertBefore crashes (#624)
* fix(web): replace custom Dialog with Radix to stop portal races

Hand-rolled createPortal siblings on document.body raced with Radix
Select/DropdownMenu/Sonner under React 19 (insertBefore/removeChild).
Use @radix-ui/react-dialog like select/dropdown adapters and bail out
overview ResizeObserver setState when values are unchanged.

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

* fix(web): stop portal removeChild races on search and shell

Move Radix portals to #skillhub-portals, drop language-switcher body
portal (match UserMenu), remove Select/Dropdown exit animations, and keep
previous search results while typing to avoid skeleton unmount churn.

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

* fix(web): systemic overlay teardown for React 19 removeChild

Render Select/Dropdown in-tree (no Portal), translate=no on overlays,
dismiss overlays on pathname change, defer navigate after close, host
Toaster in #skillhub-portals, and add recoverable RouteError UI.

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

* fix(web): harden overlay dismiss after code review

Clip decorative layer only, dismiss Dialog via overlay pointer events,
log RouteError once in useEffect, and defer skill-detail leave navigations.

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

* fix(web): batch starred highlight and defer Sonner toasts

Avoid N× per-card star fetches on /search and flushSync toast races
during React 19 list re-renders (removeChild / insertBefore).

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

* fix(web): block Chrome Translate DOM races on skill overview

Keep html/#root notranslate, sync lang with i18n, and harden overview
expand so memoized markdown + quiet ResizeObserver avoid insertBefore crashes.

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>

---------

Signed-off-by: Mikhail Neradkov <michael.neradkov@gmail.com>
2026-08-04 13:59:37 +08:00

24 lines
1.4 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN" translate="no" class="notranslate">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google" content="notranslate" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' ws: wss: http://localhost:* https://localhost:*; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"
/>
<title>SkillHub</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
</head>
<body>
<!-- translate=no: Chrome auto-translate mutates #root and races React 19 insertBefore. -->
<div id="root" translate="no" class="notranslate"></div>
<!-- Isolated host for Dialog/Sonner portals (avoids body sibling races with #root). -->
<div id="skillhub-portals" translate="no" class="notranslate"></div>
<script type="module" src="/src/bootstrap.ts"></script>
</body>
</html>