@mui/internal-docs-infra 0.11.1-canary.8 → 0.11.1

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 (319) hide show
  1. package/ChunkProvider/ChunkContext.d.mts +10 -0
  2. package/ChunkProvider/ChunkContext.mjs +15 -0
  3. package/ChunkProvider/ChunkProvider.d.mts +14 -0
  4. package/ChunkProvider/ChunkProvider.mjs +38 -0
  5. package/ChunkProvider/PreloadContext.d.mts +14 -0
  6. package/ChunkProvider/PreloadContext.mjs +18 -0
  7. package/ChunkProvider/PreloadProvider.d.mts +13 -0
  8. package/ChunkProvider/PreloadProvider.mjs +33 -0
  9. package/ChunkProvider/index.d.mts +7 -0
  10. package/ChunkProvider/index.mjs +7 -0
  11. package/ChunkProvider/types.d.mts +23 -0
  12. package/ChunkProvider/types.mjs +1 -0
  13. package/ChunkProvider/usePreload.d.mts +8 -0
  14. package/ChunkProvider/usePreload.mjs +21 -0
  15. package/CodeControllerContext/CodeControllerContext.d.mts +11 -0
  16. package/CodeControllerContext/CodeControllerContext.mjs +2 -1
  17. package/CodeHighlighter/CodeHighlighter.d.mts +15 -1
  18. package/CodeHighlighter/CodeHighlighter.mjs +97 -319
  19. package/CodeHighlighter/CodeHighlighterChunk.d.mts +42 -0
  20. package/CodeHighlighter/CodeHighlighterChunk.mjs +77 -0
  21. package/CodeHighlighter/CodeHighlighterClient.mjs +597 -128
  22. package/CodeHighlighter/CodeHighlighterContext.d.mts +57 -1
  23. package/CodeHighlighter/CodeHighlighterFallbackContext.d.mts +14 -2
  24. package/CodeHighlighter/CodeHighlighterFallbackContext.mjs +1 -3
  25. package/CodeHighlighter/CodeInitialSourceLoader.d.mts +10 -0
  26. package/CodeHighlighter/CodeInitialSourceLoader.mjs +108 -0
  27. package/CodeHighlighter/CodeSourceLoader.d.mts +11 -0
  28. package/CodeHighlighter/CodeSourceLoader.mjs +128 -0
  29. package/CodeHighlighter/buildCodeHighlighterChunkProps.d.mts +47 -0
  30. package/CodeHighlighter/buildCodeHighlighterChunkProps.mjs +61 -0
  31. package/CodeHighlighter/buildStringFallback.d.mts +29 -0
  32. package/CodeHighlighter/buildStringFallback.mjs +42 -0
  33. package/CodeHighlighter/codeToFallbackProps.d.mts +31 -2
  34. package/CodeHighlighter/codeToFallbackProps.mjs +347 -42
  35. package/CodeHighlighter/createClientProps.d.mts +17 -0
  36. package/CodeHighlighter/createClientProps.mjs +78 -0
  37. package/CodeHighlighter/errors.d.mts +6 -0
  38. package/CodeHighlighter/errors.mjs +10 -0
  39. package/CodeHighlighter/fallbackCompression.d.mts +96 -0
  40. package/CodeHighlighter/fallbackCompression.mjs +253 -0
  41. package/CodeHighlighter/fallbackFormat.d.mts +137 -0
  42. package/CodeHighlighter/fallbackFormat.mjs +422 -0
  43. package/CodeHighlighter/index.d.mts +4 -1
  44. package/CodeHighlighter/index.mjs +3 -1
  45. package/CodeHighlighter/mergeComments.d.mts +38 -0
  46. package/CodeHighlighter/mergeComments.mjs +80 -0
  47. package/CodeHighlighter/prepareInitialSource.d.mts +42 -0
  48. package/CodeHighlighter/prepareInitialSource.mjs +292 -0
  49. package/CodeHighlighter/resolveFallbackCritical.d.mts +23 -0
  50. package/CodeHighlighter/resolveFallbackCritical.mjs +44 -0
  51. package/CodeHighlighter/types.d.mts +272 -8
  52. package/CodeHighlighter/useCodeFallback.d.mts +94 -0
  53. package/CodeHighlighter/useCodeFallback.mjs +204 -0
  54. package/CodeHighlighter/useGrammarsReady.d.mts +18 -0
  55. package/CodeHighlighter/useGrammarsReady.mjs +45 -0
  56. package/CodeHighlighter/useSpeculativeCodePreload.d.mts +26 -0
  57. package/CodeHighlighter/useSpeculativeCodePreload.mjs +40 -0
  58. package/CodeHighlighter/useSpeculativeEditingPreload.d.mts +33 -0
  59. package/CodeHighlighter/useSpeculativeEditingPreload.mjs +58 -0
  60. package/CodeHighlighter/useSpeculativeGrammarPreload.d.mts +23 -0
  61. package/CodeHighlighter/useSpeculativeGrammarPreload.mjs +31 -0
  62. package/CodeHighlighter/useSpeculativeUseCodePreload.d.mts +22 -0
  63. package/CodeHighlighter/useSpeculativeUseCodePreload.mjs +41 -0
  64. package/CodeProvider/CodeContext.d.mts +47 -12
  65. package/CodeProvider/CodeContext.mjs +7 -0
  66. package/CodeProvider/CodeProvider.d.mts +4 -2
  67. package/CodeProvider/CodeProvider.mjs +40 -102
  68. package/CodeProvider/CodeProviderLazy.d.mts +40 -0
  69. package/CodeProvider/CodeProviderLazy.mjs +96 -0
  70. package/CodeProvider/constants.d.mts +26 -0
  71. package/CodeProvider/constants.mjs +24 -0
  72. package/CodeProvider/createParseSourceWorkerClient.d.mts +6 -0
  73. package/CodeProvider/createParseSourceWorkerClient.mjs +22 -2
  74. package/CodeProvider/index.d.mts +2 -1
  75. package/CodeProvider/index.mjs +9 -1
  76. package/CodeProvider/parseSourceWorker.mjs +33 -0
  77. package/CodeProvider/useCodeProviderValue.d.mts +54 -0
  78. package/CodeProvider/useCodeProviderValue.mjs +188 -0
  79. package/CoordinatedLazy/ChunkServerLoader.d.mts +25 -0
  80. package/CoordinatedLazy/ChunkServerLoader.mjs +97 -0
  81. package/CoordinatedLazy/CoordinatedContentContext.d.mts +15 -0
  82. package/CoordinatedLazy/CoordinatedContentContext.mjs +22 -0
  83. package/CoordinatedLazy/CoordinatedFallbackContext.d.mts +11 -0
  84. package/CoordinatedLazy/CoordinatedFallbackContext.mjs +13 -0
  85. package/CoordinatedLazy/CoordinatedGateContext.d.mts +14 -0
  86. package/CoordinatedLazy/CoordinatedGateContext.mjs +19 -0
  87. package/CoordinatedLazy/CoordinatedLazy.d.mts +14 -0
  88. package/CoordinatedLazy/CoordinatedLazy.mjs +86 -0
  89. package/CoordinatedLazy/CoordinatedLazyClient.d.mts +24 -0
  90. package/CoordinatedLazy/CoordinatedLazyClient.mjs +65 -0
  91. package/CoordinatedLazy/LazyContent.d.mts +26 -0
  92. package/CoordinatedLazy/LazyContent.mjs +80 -0
  93. package/CoordinatedLazy/LazyContentServer.d.mts +18 -0
  94. package/CoordinatedLazy/LazyContentServer.mjs +25 -0
  95. package/CoordinatedLazy/buildChunkRenderInputs.d.mts +8 -0
  96. package/CoordinatedLazy/buildChunkRenderInputs.mjs +35 -0
  97. package/CoordinatedLazy/createCoordinatedLazy.d.mts +32 -0
  98. package/CoordinatedLazy/createCoordinatedLazy.mjs +127 -0
  99. package/CoordinatedLazy/index.d.mts +14 -0
  100. package/CoordinatedLazy/index.mjs +18 -0
  101. package/CoordinatedLazy/resolveChunkRender.d.mts +26 -0
  102. package/CoordinatedLazy/resolveChunkRender.mjs +73 -0
  103. package/CoordinatedLazy/types.d.mts +408 -0
  104. package/CoordinatedLazy/types.mjs +1 -0
  105. package/CoordinatedLazy/useChunk.d.mts +30 -0
  106. package/CoordinatedLazy/useChunk.mjs +135 -0
  107. package/CoordinatedLazy/useCoordinatedFallback.d.mts +12 -0
  108. package/CoordinatedLazy/useCoordinatedFallback.mjs +40 -0
  109. package/CoordinatedLazy/useCoordinatedSwap.d.mts +16 -0
  110. package/CoordinatedLazy/useCoordinatedSwap.mjs +124 -0
  111. package/LICENSE +1 -1
  112. package/abstractCreateDemo/abstractCreateDemo.d.mts +54 -3
  113. package/abstractCreateDemo/abstractCreateDemo.mjs +47 -7
  114. package/abstractCreateDemo/resolveDemoFlag.d.mts +20 -0
  115. package/abstractCreateDemo/resolveDemoFlag.mjs +25 -0
  116. package/abstractCreateStream/abstractCreateStream.d.mts +18 -0
  117. package/abstractCreateStream/abstractCreateStream.mjs +45 -0
  118. package/abstractCreateStream/index.d.mts +2 -0
  119. package/abstractCreateStream/index.mjs +1 -0
  120. package/abstractCreateStream/types.d.mts +34 -0
  121. package/abstractCreateStream/types.mjs +1 -0
  122. package/abstractCreateTypes/TypeCode.mjs +12 -11
  123. package/abstractCreateTypes/typesToJsx.mjs +30 -9
  124. package/cli/ensureDemoClients.mjs +4 -148
  125. package/cli/ensureDemoPages.d.mts +45 -0
  126. package/cli/ensureDemoPages.mjs +99 -0
  127. package/cli/fileUtils/index.d.mts +11 -0
  128. package/cli/fileUtils/index.mjs +48 -0
  129. package/cli/findDemoIndexFiles.d.mts +15 -0
  130. package/cli/findDemoIndexFiles.mjs +121 -0
  131. package/cli/index.mjs +1 -1
  132. package/cli/loadNextConfig.d.mts +25 -0
  133. package/cli/loadNextConfig.mjs +60 -1
  134. package/cli/runBrowser.mjs +1 -1
  135. package/cli/runValidate.mjs +44 -1
  136. package/package.json +85 -5
  137. package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasis.mjs +30 -0
  138. package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasisLazy.d.mts +17 -0
  139. package/pipeline/enhanceCodeEmphasis/enhanceCodeEmphasisLazy.mjs +52 -0
  140. package/pipeline/hastUtils/frameFallbackFromSpans.d.mts +18 -0
  141. package/pipeline/hastUtils/frameFallbackFromSpans.mjs +24 -0
  142. package/pipeline/hastUtils/hast.d.mts +27 -0
  143. package/pipeline/hastUtils/hastCompression.d.mts +3 -1
  144. package/pipeline/hastUtils/hastCompression.mjs +9 -1
  145. package/pipeline/hastUtils/hastDecompress.mjs +10 -4
  146. package/pipeline/hastUtils/hastDictionary.mjs +9 -0
  147. package/pipeline/hastUtils/hastUtils.d.mts +4 -3
  148. package/pipeline/hastUtils/hastUtils.mjs +24 -12
  149. package/pipeline/hastUtils/index.d.mts +2 -1
  150. package/pipeline/hastUtils/index.mjs +2 -1
  151. package/pipeline/hastUtils/stripHighlightingSpans.d.mts +6 -2
  152. package/pipeline/hastUtils/stripHighlightingSpans.mjs +22 -10
  153. package/pipeline/lintJavascriptDemoFocus/lintJavascriptDemoFocus.mjs +10 -7
  154. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.d.mts +31 -13
  155. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransform.mjs +50 -55
  156. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransformWithComments.d.mts +78 -0
  157. package/pipeline/loadIsomorphicCodeVariant/applyCodeTransformWithComments.mjs +405 -0
  158. package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.d.mts +5 -5
  159. package/pipeline/loadIsomorphicCodeVariant/computeHastDeltas.mjs +36 -66
  160. package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.d.mts +23 -0
  161. package/pipeline/loadIsomorphicCodeVariant/decodeHastSource.mjs +92 -0
  162. package/pipeline/loadIsomorphicCodeVariant/decodeSource.d.mts +19 -0
  163. package/pipeline/loadIsomorphicCodeVariant/decodeSource.mjs +25 -0
  164. package/pipeline/loadIsomorphicCodeVariant/decodeSourceToText.d.mts +17 -0
  165. package/pipeline/loadIsomorphicCodeVariant/decodeSourceToText.mjs +26 -0
  166. package/pipeline/loadIsomorphicCodeVariant/diffHast.d.mts +26 -2
  167. package/pipeline/loadIsomorphicCodeVariant/diffHast.mjs +563 -19
  168. package/pipeline/loadIsomorphicCodeVariant/embedTransforms.d.mts +49 -0
  169. package/pipeline/loadIsomorphicCodeVariant/embedTransforms.mjs +152 -0
  170. package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.d.mts +51 -0
  171. package/pipeline/loadIsomorphicCodeVariant/findExpandingRanges.mjs +161 -0
  172. package/pipeline/loadIsomorphicCodeVariant/flattenCodeVariant.mjs +6 -3
  173. package/pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.d.mts +12 -0
  174. package/pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.mjs +44 -0
  175. package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.d.mts +16 -0
  176. package/pipeline/loadIsomorphicCodeVariant/getInitialVisibleSourceLines.mjs +74 -0
  177. package/pipeline/loadIsomorphicCodeVariant/loadCodeFallback.mjs +17 -5
  178. package/pipeline/loadIsomorphicCodeVariant/loadIsomorphicCodeVariant.mjs +229 -15
  179. package/pipeline/loadIsomorphicCodeVariant/transformSource.d.mts +2 -2
  180. package/pipeline/loadIsomorphicCodeVariant/transformSource.mjs +56 -22
  181. package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.d.mts +18 -0
  182. package/pipeline/loadPrecomputedCodeHighlighter/loadPrecomputedCodeHighlighter.mjs +11 -7
  183. package/pipeline/loadServerTypes/hastTypeUtils.d.mts +2 -2
  184. package/pipeline/loadServerTypes/hastTypeUtils.mjs +4 -4
  185. package/pipeline/loadServerTypes/loadServerTypes.mjs +1 -1
  186. package/pipeline/loadServerTypesMeta/extractJSDocText.d.mts +14 -0
  187. package/pipeline/loadServerTypesMeta/extractJSDocText.mjs +60 -0
  188. package/pipeline/loadServerTypesMeta/processTypes.mjs +43 -46
  189. package/pipeline/loadServerTypesText/order.mjs +1 -1
  190. package/pipeline/loadServerTypesText/parseTypesMarkdown.mjs +3 -1
  191. package/pipeline/loaderUtils/index.d.mts +0 -1
  192. package/pipeline/loaderUtils/index.mjs +0 -1
  193. package/pipeline/loaderUtils/parseImportsAndComments.d.mts +5 -1
  194. package/pipeline/loaderUtils/parseImportsAndComments.mjs +19 -9
  195. package/pipeline/loaderUtils/resolveModulePath.mjs +23 -1
  196. package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.d.mts +12 -0
  197. package/pipeline/parseCreateFactoryCall/parseCreateFactoryCall.mjs +17 -13
  198. package/pipeline/parseSource/addLineGutters.mjs +45 -11
  199. package/pipeline/parseSource/calculateFrameRanges.d.mts +22 -0
  200. package/pipeline/parseSource/calculateFrameRanges.mjs +69 -25
  201. package/pipeline/parseSource/detectGrammarScopes.d.mts +13 -0
  202. package/pipeline/parseSource/detectGrammarScopes.mjs +35 -0
  203. package/pipeline/parseSource/extendSyntaxTokens.mjs +501 -43
  204. package/pipeline/parseSource/frameVisibility.d.mts +47 -0
  205. package/pipeline/parseSource/frameVisibility.mjs +114 -0
  206. package/pipeline/parseSource/grammarCache.d.mts +33 -0
  207. package/pipeline/parseSource/grammarCache.mjs +73 -0
  208. package/pipeline/parseSource/grammarLoaders.d.mts +14 -0
  209. package/pipeline/parseSource/grammarLoaders.mjs +24 -0
  210. package/pipeline/parseSource/grammarMaps.d.mts +21 -1
  211. package/pipeline/parseSource/grammarMaps.mjs +36 -0
  212. package/pipeline/parseSource/isFrameSpan.d.mts +19 -0
  213. package/pipeline/parseSource/isFrameSpan.mjs +24 -0
  214. package/pipeline/parseSource/parseSource.d.mts +41 -6
  215. package/pipeline/parseSource/parseSource.mjs +184 -36
  216. package/pipeline/parseSource/redistributeFrameFallbacks.d.mts +40 -0
  217. package/pipeline/parseSource/redistributeFrameFallbacks.mjs +138 -0
  218. package/pipeline/parseSource/restructureFrames.d.mts +5 -0
  219. package/pipeline/parseSource/restructureFrames.mjs +179 -16
  220. package/pipeline/syncPageIndex/metadataToMarkdown.mjs +6 -2
  221. package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.d.mts +26 -0
  222. package/pipeline/transformHtmlCodeBlock/transformHtmlCodeBlock.mjs +181 -114
  223. package/pipeline/transformHtmlCodeInline/removeSuffixFromHighlightedNodes.d.mts +12 -0
  224. package/pipeline/transformHtmlCodeInline/removeSuffixFromHighlightedNodes.mjs +52 -0
  225. package/pipeline/transformHtmlCodeInline/transformHtmlCodeInline.mjs +22 -1
  226. package/pipeline/transformTypescriptToJavascript/removeTypes.d.mts +5 -8
  227. package/pipeline/transformTypescriptToJavascript/removeTypes.mjs +27 -93
  228. package/useCode/EditableEngine.d.mts +233 -0
  229. package/useCode/EditableEngine.mjs +1712 -0
  230. package/useCode/EditingEngine.d.mts +13 -0
  231. package/useCode/EditingEngine.mjs +14 -0
  232. package/useCode/Pre.browser.mjs +5 -1
  233. package/useCode/Pre.d.mts +127 -1
  234. package/useCode/Pre.mjs +417 -165
  235. package/useCode/SourceEditingEngine.d.mts +50 -0
  236. package/useCode/SourceEditingEngine.mjs +461 -0
  237. package/useCode/TransformEngine.d.mts +39 -0
  238. package/useCode/TransformEngine.mjs +208 -0
  239. package/useCode/editingEngineCache.d.mts +29 -0
  240. package/useCode/editingEngineCache.mjs +68 -0
  241. package/useCode/sourceLineCounts.d.mts +80 -0
  242. package/useCode/sourceLineCounts.mjs +284 -0
  243. package/useCode/subscribeToggleNudge.d.mts +3 -0
  244. package/useCode/subscribeToggleNudge.mjs +95 -0
  245. package/useCode/transformEngineCache.d.mts +21 -0
  246. package/useCode/transformEngineCache.mjs +60 -0
  247. package/useCode/useCode.d.mts +140 -1
  248. package/useCode/useCode.mjs +250 -19
  249. package/useCode/useCodeUtils.d.mts +131 -20
  250. package/useCode/useCodeUtils.mjs +267 -194
  251. package/useCode/useCopyFunctionality.d.mts +13 -1
  252. package/useCode/useCopyFunctionality.mjs +39 -9
  253. package/useCode/useEditable.browser.mjs +10 -2
  254. package/useCode/useEditable.d.mts +27 -106
  255. package/useCode/useEditable.integration.browser.d.mts +1 -0
  256. package/useCode/useEditable.integration.browser.mjs +870 -0
  257. package/useCode/useEditable.mjs +198 -1247
  258. package/useCode/useEditableUtils.d.mts +50 -1
  259. package/useCode/useEditableUtils.mjs +29 -0
  260. package/useCode/useFileNavigation.d.mts +91 -3
  261. package/useCode/useFileNavigation.mjs +201 -41
  262. package/useCode/useHighlightGate.d.mts +17 -0
  263. package/useCode/useHighlightGate.mjs +147 -0
  264. package/useCode/useSourceEditing.d.mts +8 -0
  265. package/useCode/useSourceEditing.mjs +158 -314
  266. package/useCode/useSourceEnhancing.d.mts +5 -1
  267. package/useCode/useSourceEnhancing.mjs +22 -36
  268. package/useCode/useTransformManagement.d.mts +93 -5
  269. package/useCode/useTransformManagement.mjs +496 -28
  270. package/useCode/useTransitionPhase.d.mts +24 -0
  271. package/useCode/useTransitionPhase.mjs +49 -0
  272. package/useCode/useUIState.d.mts +2 -2
  273. package/useCode/useUIState.mjs +8 -8
  274. package/useCode/useVariantSelection.d.mts +130 -6
  275. package/useCode/useVariantSelection.mjs +529 -93
  276. package/useCodeWindow/useCodeWindow.d.mts +19 -2
  277. package/useCodeWindow/useCodeWindow.mjs +98 -71
  278. package/useCoordinated/coordinatePreference.d.mts +439 -0
  279. package/useCoordinated/coordinatePreference.mjs +951 -0
  280. package/useCoordinated/coordinatePreference.testUtils.d.mts +21 -0
  281. package/useCoordinated/coordinatePreference.testUtils.mjs +69 -0
  282. package/useCoordinated/createSettleGate.d.mts +96 -0
  283. package/useCoordinated/createSettleGate.mjs +171 -0
  284. package/useCoordinated/index.d.mts +8 -0
  285. package/useCoordinated/index.mjs +8 -0
  286. package/useCoordinated/layoutShiftGate.d.mts +24 -0
  287. package/useCoordinated/layoutShiftGate.mjs +79 -0
  288. package/useCoordinated/pageSettleGate.d.mts +11 -0
  289. package/useCoordinated/pageSettleGate.mjs +13 -0
  290. package/useCoordinated/scheduleTasks.d.mts +23 -0
  291. package/useCoordinated/scheduleTasks.mjs +45 -0
  292. package/useCoordinated/useCoordinated.d.mts +193 -0
  293. package/useCoordinated/useCoordinated.mjs +469 -0
  294. package/useCoordinated/useCoordinatedLazy.d.mts +17 -0
  295. package/useCoordinated/useCoordinatedLazy.mjs +38 -0
  296. package/useCoordinated/useCoordinatedLocalStorage.d.mts +16 -0
  297. package/useCoordinated/useCoordinatedLocalStorage.mjs +22 -0
  298. package/useCoordinated/useCoordinatedPreference.d.mts +20 -0
  299. package/useCoordinated/useCoordinatedPreference.mjs +26 -0
  300. package/useCoordinated/useSettleGate.d.mts +11 -0
  301. package/useCoordinated/useSettleGate.mjs +34 -0
  302. package/useDemo/exportVariant.d.mts +12 -5
  303. package/useDemo/exportVariant.mjs +59 -5
  304. package/useDemo/useDemo.d.mts +5 -2
  305. package/useScrollAnchor/useScrollAnchor.mjs +28 -5
  306. package/useStream/index.d.mts +6 -0
  307. package/useStream/index.mjs +6 -0
  308. package/useStream/streamChunks.d.mts +23 -0
  309. package/useStream/streamChunks.mjs +85 -0
  310. package/useStream/types.d.mts +45 -0
  311. package/useStream/types.mjs +1 -0
  312. package/useStream/useStream.d.mts +57 -0
  313. package/useStream/useStream.mjs +119 -0
  314. package/useStream/useStreamController.d.mts +15 -0
  315. package/useStream/useStreamController.mjs +90 -0
  316. package/withDocsInfra/withDocsInfra.d.mts +19 -0
  317. package/withDocsInfra/withDocsInfra.mjs +13 -5
  318. package/pipeline/loaderUtils/convertCommentsToOneIndexed.d.mts +0 -8
  319. package/pipeline/loaderUtils/convertCommentsToOneIndexed.mjs +0 -16
@@ -7,12 +7,32 @@ import { maybeCodeInitialData } from "../pipeline/loadIsomorphicCodeVariant/mayb
7
7
  import { hasAllVariants } from "../pipeline/loadIsomorphicCodeVariant/hasAllCodeVariants.mjs";
8
8
  import { CodeHighlighterFallbackContext } from "./CodeHighlighterFallbackContext.mjs";
9
9
  import { useControlledCode } from "../CodeControllerContext/index.mjs";
10
- import { codeToFallbackProps } from "./codeToFallbackProps.mjs";
10
+ import { codeToFallbackProps, deriveFallbacksFromCode, stripFallbackHastsFromCode } from "./codeToFallbackProps.mjs";
11
+ import { resolveFallbackCritical } from "./resolveFallbackCritical.mjs";
12
+ import { decompressResidualFallbacks, residualDictionaryText, scatterResidualFallbacks } from "./fallbackCompression.mjs";
11
13
  import { mergeCodeMetadata } from "../pipeline/loadIsomorphicCodeVariant/mergeCodeMetadata.mjs";
14
+ import { getAvailableTransforms } from "../pipeline/loadIsomorphicCodeVariant/getAvailableTransforms.mjs";
15
+ import { useSpeculativeCodePreload } from "./useSpeculativeCodePreload.mjs";
16
+ import { useSpeculativeEditingPreload } from "./useSpeculativeEditingPreload.mjs";
17
+ import { useSpeculativeUseCodePreload } from "./useSpeculativeUseCodePreload.mjs";
18
+ import { useSpeculativeGrammarPreload } from "./useSpeculativeGrammarPreload.mjs";
19
+ import { useGrammarsReady } from "./useGrammarsReady.mjs";
20
+ import { detectGrammarScopes } from "../pipeline/parseSource/detectGrammarScopes.mjs";
21
+ import { useChunk } from "../CoordinatedLazy/useChunk.mjs";
22
+ import { useCoordinatedSwap } from "../CoordinatedLazy/useCoordinatedSwap.mjs";
23
+ import { CoordinatedFallbackContext } from "../CoordinatedLazy/CoordinatedFallbackContext.mjs";
24
+ import { CoordinatedContentContext } from "../CoordinatedLazy/CoordinatedContentContext.mjs";
25
+ import { requestIdle } from "../useCoordinated/scheduleTasks.mjs";
12
26
  import * as Errors from "./errors.mjs";
13
27
  import { jsx as _jsx } from "react/jsx-runtime";
14
28
  const DEBUG = false; // Set to true for debugging purposes
15
29
 
30
+ // `useChunk` is the chunk loader/renderer, but here we use only its loading
31
+ // engine (load-when-enabled + abort + `refresh()` with stale-while-revalidate),
32
+ // so the content component is an unused placeholder.
33
+ function NoopChunkContent() {
34
+ return null;
35
+ }
16
36
  function useInitialData({
17
37
  variants,
18
38
  variantName,
@@ -25,14 +45,15 @@ function useInitialData({
25
45
  fallbackUsesAllVariants,
26
46
  isControlled,
27
47
  globalsCode,
28
- setProcessedGlobalsCode
48
+ setProcessedGlobalsCode,
49
+ handleSetFallbackHasts
29
50
  }) {
30
51
  const {
31
52
  sourceParser,
32
53
  loadCodeMeta,
33
54
  loadVariantMeta,
34
55
  loadSource,
35
- loadCodeFallback,
56
+ loadCodeFallbackLoader,
36
57
  sourceEnhancers
37
58
  } = useCodeContext();
38
59
  const {
@@ -45,7 +66,11 @@ function useInitialData({
45
66
  // URL is required for loading fallback data
46
67
  throw new Errors.ErrorCodeHighlighterClientMissingUrlForFallback();
47
68
  }
48
- if (!loadCodeFallback) {
69
+
70
+ // Validate against the loader accessor's presence (synchronously defined
71
+ // whenever a CodeProvider is mounted) - never against the resolved fn, so we
72
+ // don't throw merely because a lazy import is still in flight.
73
+ if (!loadCodeFallbackLoader) {
49
74
  throw new Errors.ErrorCodeHighlighterClientMissingLoadFallbackCode(url);
50
75
  }
51
76
  }
@@ -53,22 +78,27 @@ function useInitialData({
53
78
  // Signal to downstream loaders that a fallback fetch is pending. Used to gate
54
79
  // `useAllVariants` so it can reuse the data populated by the fallback rather
55
80
  // than racing it and re-fetching the same variant.
56
- const fallbackPending = Boolean(needsFallback && url && loadCodeFallback);
81
+ const fallbackPending = Boolean(needsFallback && url && loadCodeFallbackLoader);
57
82
 
83
+ // The fallback load runs through `useChunk` too (same loading engine as the
84
+ // full load) — the body is unchanged (it still calls `setCode` / hoists /
85
+ // `setProcessedGlobalsCode` directly; `code` stays owned by the component).
86
+ // `controlled: !needsFallback` is the gate.
58
87
  // TODO: fallbackInitialRenderOnly option? this would mean we can't fetch fallback data on the client side
59
- // Load initial data if not provided
60
- React.useEffect(() => {
61
- if (!needsFallback || !url || !loadCodeFallback) {
62
- return;
63
- }
64
-
65
- // TODO: abort controller
66
-
67
- (async () => {
88
+ const fallbackSource = React.useMemo(() => ({
89
+ mode: 'data',
90
+ load: async (_options, signal) => {
91
+ if (!url || !loadCodeFallbackLoader) {
92
+ return code ?? {};
93
+ }
68
94
  if (DEBUG) {
69
95
  // eslint-disable-next-line no-console
70
96
  console.log('Loading initial data for CodeHighlighterClient: ', reason);
71
97
  }
98
+
99
+ // Lazily resolve the heavy fallback loader (instant under an eager
100
+ // CodeProvider, a deduped fetch under CodeProviderLazy) before loading.
101
+ const loadCodeFallback = await loadCodeFallbackLoader();
72
102
  const loaded = await loadCodeFallback(url, variantName, code, {
73
103
  shouldHighlight: highlightAfter === 'init',
74
104
  fallbackUsesExtraFiles,
@@ -82,19 +112,47 @@ function useInitialData({
82
112
  variants,
83
113
  globalsCode // Let loadCodeFallback handle processing
84
114
  }).catch(error => ({
85
- error
115
+ error: error instanceof Error ? error : new Error(String(error))
86
116
  }));
87
117
  if ('error' in loaded) {
88
118
  console.error(new Errors.ErrorCodeHighlighterClientLoadFallbackFailure(loaded.error));
89
- } else {
90
- setCode(loaded.code);
119
+ return code ?? {};
120
+ }
121
+
122
+ // Fold each variant's highlighted-visible `fallbackCritical` over its plain
123
+ // `fallback` (under `highlightAt: 'init'`) and strip the staging field, so the
124
+ // hoisted loading fallback is already highlighted and nothing leaks to the
125
+ // content. `collapseToEmpty` isn't threaded into the client here, so the `false`
126
+ // form is assumed: under collapse-to-empty this may promote a few frames that are
127
+ // then CSS-hidden, but that is harmless — the promoted text is byte-identical
128
+ // (a valid dictionary) and the frames never paint.
129
+ const resolved = resolveFallbackCritical(loaded.code, highlightAfter, false) ?? loaded.code;
130
+
131
+ // Strip fallbacks from code and hoist them directly
132
+ const {
133
+ strippedCode,
134
+ allFallbackHasts
135
+ } = stripFallbackHastsFromCode(resolved, variantName, fallbackUsesExtraFiles, fallbackUsesAllVariants);
136
+ if (!signal.aborted) {
137
+ setCode(strippedCode);
138
+ for (const [variant, hasts] of Object.entries(allFallbackHasts)) {
139
+ handleSetFallbackHasts(variant, hasts);
140
+ }
91
141
  // Store processed globalsCode from loadCodeFallback result
92
142
  if (loaded.processedGlobalsCode) {
93
143
  setProcessedGlobalsCode(loaded.processedGlobalsCode);
94
144
  }
95
145
  }
96
- })();
97
- }, [initialData, reason, needsFallback, variantName, code, setCode, highlightAfter, url, sourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallback]);
146
+ return strippedCode;
147
+ }
148
+ }), [reason, variantName, code, setCode, highlightAfter, url, sourceParser, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, fallbackUsesExtraFiles, fallbackUsesAllVariants, fileName, variants, globalsCode, setProcessedGlobalsCode, loadCodeFallbackLoader, handleSetFallbackHasts]);
149
+ const fallbackConfig = React.useMemo(() => ({
150
+ ChunkContent: NoopChunkContent,
151
+ source: fallbackSource
152
+ }), [fallbackSource]);
153
+ useChunk(fallbackConfig, {
154
+ controlled: !needsFallback
155
+ });
98
156
  return {
99
157
  fallbackPending
100
158
  };
@@ -115,7 +173,7 @@ function useAllVariants({
115
173
  loadCodeMeta,
116
174
  loadVariantMeta,
117
175
  loadSource,
118
- loadIsomorphicCodeVariant,
176
+ loadIsomorphicCodeVariantLoader,
119
177
  sourceEnhancers
120
178
  } = useCodeContext();
121
179
  const needsData = !readyForContent && !isControlled && !fallbackPending;
@@ -126,7 +184,7 @@ function useAllVariants({
126
184
  if (!url) {
127
185
  throw new Errors.ErrorCodeHighlighterClientMissingUrlForVariants();
128
186
  }
129
- if (!loadIsomorphicCodeVariant) {
187
+ if (!loadIsomorphicCodeVariantLoader) {
130
188
  throw new Errors.ErrorCodeHighlighterClientMissingLoadVariant(url);
131
189
  }
132
190
  if (!code && !loadCodeMeta) {
@@ -152,16 +210,24 @@ function useAllVariants({
152
210
  throw new Errors.ErrorCodeHighlighterClientMissingLoadSourceForUnloadedUrls();
153
211
  }
154
212
  }
155
- }, [code, globalsCode, loadCodeMeta, loadIsomorphicCodeVariant, loadSource, needsData, url]);
156
- React.useEffect(() => {
157
- if (!needsData || !url || !loadIsomorphicCodeVariant) {
158
- return;
159
- }
213
+ }, [code, globalsCode, loadCodeMeta, loadIsomorphicCodeVariantLoader, loadSource, needsData, url]);
160
214
 
161
- // TODO: abort controller
162
-
163
- (async () => {
215
+ // The full-variant load runs through `useChunk` so it inherits the abstraction's
216
+ // load-when-enabled + abort + `refresh()` (stale-while-revalidate) engine. The
217
+ // loader body is unchanged — it still calls `setCode` / `setProcessedGlobalsCode`
218
+ // directly (the chunk's own `data`/`loading` are unused; `code` stays owned by
219
+ // this component). `controlled: !needsData` is the gate: when the data isn't
220
+ // needed the chunk treats itself as already loaded and never runs the loader.
221
+ const fullVariantSource = React.useMemo(() => ({
222
+ mode: 'data',
223
+ load: async (_options, signal) => {
224
+ if (!url || !loadIsomorphicCodeVariantLoader) {
225
+ return code ?? {};
226
+ }
164
227
  try {
228
+ // Lazily resolve the heavy variant loader (instant under an eager
229
+ // CodeProvider, a deduped fetch under CodeProviderLazy) before loading.
230
+ const loadIsomorphicCodeVariant = await loadIsomorphicCodeVariantLoader();
165
231
  let loadedCode = code;
166
232
  if (!loadedCode) {
167
233
  if (!loadCodeMeta) {
@@ -189,7 +255,9 @@ function useAllVariants({
189
255
  return item;
190
256
  }));
191
257
  // Store processed globalsCode in state for future use
192
- setProcessedGlobalsCode(globalsCodeObjects);
258
+ if (!signal.aborted) {
259
+ setProcessedGlobalsCode(globalsCodeObjects);
260
+ }
193
261
  }
194
262
 
195
263
  // Load variant data without parsing or transforming
@@ -210,7 +278,7 @@ function useAllVariants({
210
278
  name,
211
279
  variant
212
280
  })).catch(error => ({
213
- error
281
+ error: error instanceof Error ? error : new Error(String(error))
214
282
  }));
215
283
  }));
216
284
  const resultCode = {};
@@ -222,30 +290,37 @@ function useAllVariants({
222
290
  resultCode[item.name] = item.variant.code;
223
291
  }
224
292
  }
293
+
294
+ // Strip the staging `fallbackCritical` before it enters `code` state and
295
+ // reaches the content. The full load runs with `disableParsing`, so the source
296
+ // is a raw string and no `fallbackCritical` is produced here — the strip is
297
+ // purely defensive, hence the strip-only `'idle'` (promotion is `'init'`-gated).
298
+ const resolvedResultCode = resolveFallbackCritical(resultCode, 'idle', false) ?? resultCode;
225
299
  if (errors.length > 0) {
226
300
  console.error(new Errors.ErrorCodeHighlighterClientLoadVariantsFailure(url, errors));
227
- } else {
228
- setCode(resultCode);
301
+ } else if (!signal.aborted) {
302
+ setCode(resolvedResultCode);
229
303
  }
304
+ return resolvedResultCode;
230
305
  } catch (error) {
231
306
  console.error(new Errors.ErrorCodeHighlighterClientLoadAllVariantsFailure(url, error));
307
+ return code ?? {};
232
308
  }
233
- })();
234
- }, [needsData, variants, url, code, setCode, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, processedGlobalsCode, globalsCode, setProcessedGlobalsCode, loadIsomorphicCodeVariant]);
309
+ }
310
+ }), [variants, url, code, setCode, loadSource, loadVariantMeta, loadCodeMeta, sourceEnhancers, processedGlobalsCode, globalsCode, setProcessedGlobalsCode, loadIsomorphicCodeVariantLoader]);
311
+ const fullVariantConfig = React.useMemo(() => ({
312
+ ChunkContent: NoopChunkContent,
313
+ source: fullVariantSource
314
+ }), [fullVariantSource]);
315
+ const {
316
+ refresh: refreshAllVariants
317
+ } = useChunk(fullVariantConfig, {
318
+ controlled: !needsData
319
+ });
235
320
  return {
236
- readyForContent
321
+ refresh: refreshAllVariants
237
322
  };
238
323
  }
239
- function yieldToMain() {
240
- if (globalThis.scheduler?.yield) {
241
- return globalThis.scheduler.yield();
242
- }
243
-
244
- // Fall back to yielding with setTimeout.
245
- return new Promise(resolve => {
246
- setTimeout(resolve, 0);
247
- });
248
- }
249
324
  function useCodeParsing({
250
325
  code,
251
326
  readyForContent,
@@ -262,22 +337,18 @@ function useCodeParsing({
262
337
  const [isHighlightAllowed, setIsHighlightAllowed] = React.useState(highlightAfter === 'init' || highlightAfter === 'hydration' && isHydrated);
263
338
  React.useEffect(() => {
264
339
  if (highlightAfter === 'idle') {
265
- const requestIdleCallback = window.requestIdleCallback ?? setTimeout;
266
- const cancelIdleCallback = window.cancelIdleCallback ?? clearTimeout;
267
- const idleRequest = requestIdleCallback(() => {
268
- setIsHighlightAllowed(true);
269
- });
270
- return () => cancelIdleCallback(idleRequest);
340
+ return requestIdle(() => setIsHighlightAllowed(true));
271
341
  }
272
342
  return undefined;
273
343
  }, [highlightAfter]);
274
344
 
275
- // Update highlight allowed state when hydration completes
345
+ // Highlight instantly once hydrated, as a non-blocking client transition,
346
+ // rather than deferring to a scheduled task. (`highlightAt: 'idle'` above is
347
+ // the mode that deliberately keeps the unhighlighted first paint and swaps in
348
+ // the highlighted tree on a later idle render.)
276
349
  React.useEffect(() => {
277
350
  if (highlightAfter === 'hydration' && isHydrated) {
278
- // we should ensure that each code highlighter is enhanced as a separate task
279
- // this should run from top to bottom
280
- yieldToMain().then(() => setIsHighlightAllowed(true));
351
+ React.startTransition(() => setIsHighlightAllowed(true));
281
352
  }
282
353
  }, [highlightAfter, isHydrated]);
283
354
 
@@ -289,9 +360,31 @@ function useCodeParsing({
289
360
  return isHighlightAllowed;
290
361
  }, [readyForContent, isHighlightAllowed]);
291
362
 
363
+ // Memoize the "every variant is already in HAST form" check so it
364
+ // doesn't re-walk the variant + extraFiles trees on every render.
365
+ // Used both as the short-circuit inside the `parseCode` memo (fully-
366
+ // precomputed sites skip parsing entirely) and as the unmemoized
367
+ // `waitingForParsedCode` gate just below.
368
+ const allVariantsAlreadyHighlighted = React.useMemo(() => code ? hasAllVariants(Object.keys(code), code, true) : false, [code]);
369
+
370
+ // Under `CodeProviderLazy` grammars load per-language and on demand, so the
371
+ // client parse must wait until the grammars for this block's scopes are
372
+ // registered — otherwise `parseSource` falls back to plain text. Gate the
373
+ // parse memo on readiness so the block keeps its fallback until they land (no
374
+ // plain-text flash), then highlights. Synchronously ready when warm (the
375
+ // speculative preload primed them, or under an eager `CodeProvider`), so this
376
+ // adds no delay on the common path.
377
+ const grammarScopes = React.useMemo(() => code ? detectGrammarScopes(code) : [], [code]);
378
+ const grammarsReady = useGrammarsReady(grammarScopes, !!code && shouldHighlight && !allVariantsAlreadyHighlighted);
379
+
292
380
  // Parse the internal code state when ready and timing conditions are met
293
381
  const parsedCode = React.useMemo(() => {
294
- if (!code || !shouldHighlight || hasAllVariants(Object.keys(code), code, true)) {
382
+ if (!code || !shouldHighlight || allVariantsAlreadyHighlighted) {
383
+ return undefined;
384
+ }
385
+ if (!grammarsReady) {
386
+ // Grammars for this block's scopes are still loading; keep the fallback and
387
+ // re-run once `useGrammarsReady` flips (mirrors the `!parseSource` wait).
295
388
  return undefined;
296
389
  }
297
390
  if (!parseSource) {
@@ -317,54 +410,136 @@ function useCodeParsing({
317
410
  return undefined;
318
411
  }
319
412
  return parseCode(code, parseSource);
320
- }, [code, shouldHighlight, sourceParser, parseSource, parseCode, forceClient, url]);
321
- const deferHighlight = !shouldHighlight;
413
+ }, [code, shouldHighlight, allVariantsAlreadyHighlighted, grammarsReady, sourceParser, parseSource, parseCode, forceClient, url]);
414
+
415
+ // Keep highlighting deferred until parsed HAST is actually available for the
416
+ // variants that need it. `shouldHighlight` can flip true ~30ms after
417
+ // hydration, but `parseCode` only runs once the async `sourceParser` promise
418
+ // resolves. Without this wait, downstream consumers (e.g. the transform
419
+ // swap) would commit while the visible variant is still rendered from its
420
+ // raw string source, producing a structure swap on the DOM moments later.
421
+ const waitingForParsedCode = shouldHighlight && !!code && !allVariantsAlreadyHighlighted && !parsedCode;
422
+
423
+ // Only signal `deferHighlight` while a highlight pass is actively in
424
+ // flight. When `shouldHighlight` is `false` (e.g. `highlightAt: 'idle'`
425
+ // before the idle window fires, or `'view'` before the block scrolls
426
+ // into view) we render the un-highlighted source as-is — downstream
427
+ // consumers like `useTransformManagement`'s `awaitHighlight` gate must
428
+ // commit eagerly against that source instead of blocking the barrier
429
+ // indefinitely. Once the trigger fires, `shouldHighlight` flips true,
430
+ // `waitingForParsedCode` becomes true while `parseCode` runs, and
431
+ // `deferHighlight` engages for the brief window before the next
432
+ // commit paints the highlighted tree.
433
+ const deferHighlight = waitingForParsedCode;
434
+
435
+ // Render-side readiness gate. `<Pre>` (via `useCode.shouldHighlight`)
436
+ // needs to know whether the published `code` should be rendered as
437
+ // highlighted HAST *now*. That answer is false in two distinct
438
+ // windows that `deferHighlight` deliberately collapses out:
439
+ // 1. The trigger for `highlightAt: 'hydration' | 'idle' | 'visible'`
440
+ // hasn't fired yet — `shouldHighlight` is still false. The
441
+ // precomputed `codeWithGlobals` already contains HAST, so
442
+ // without a render-side gate `<Pre>` would render highlighted
443
+ // spans on the SSR pass and on first client paint, defeating
444
+ // the whole point of deferred highlighting.
445
+ // 2. The trigger has fired (`shouldHighlight = true`) but
446
+ // `parseCode` hasn't resolved yet (`waitingForParsedCode`).
447
+ // Rendering would briefly flash un-highlighted text against
448
+ // the same tree position before the highlighted HAST lands.
449
+ //
450
+ // `highlightReady` is the inverse of the pre-`e7cc08b7` wide
451
+ // `deferHighlight` semantic, exposed separately so the narrow
452
+ // `deferHighlight` (barrier consumers only block on real in-flight
453
+ // work) and the render gate can diverge without coupling.
454
+ const highlightReady = shouldHighlight && !waitingForParsedCode;
322
455
  return {
323
456
  parsedCode,
324
- deferHighlight
457
+ deferHighlight,
458
+ highlightReady
325
459
  };
326
460
  }
327
461
  function useCodeTransforms({
328
462
  parsedCode,
463
+ loadedCode,
329
464
  variantName
330
465
  }) {
331
466
  const {
332
467
  sourceParser,
333
- getAvailableTransforms,
334
- computeHastDeltas
468
+ computeHastDeltasLoader
335
469
  } = useCodeContext();
336
- const [transformedCode, setTransformedCode] = React.useState(undefined);
470
+ // Track which `parsedCode` the cached `transformedCode` was computed from
471
+ // so a fresh `parsedCode` (e.g. a newly-loaded variant being added to the
472
+ // map) re-engages `waitingForTransformedCode` instead of returning the
473
+ // stale output for one render cycle. Storing input + output together lets
474
+ // callers detect staleness with reference equality.
475
+ const [transformedState, setTransformedState] = React.useState({});
337
476
 
338
477
  // Get available transforms from the current variant (separate memo for efficiency)
339
- const availableTransforms = React.useMemo(() => {
340
- if (!getAvailableTransforms) {
341
- return [];
342
- }
343
- return getAvailableTransforms(parsedCode, variantName);
344
- }, [parsedCode, variantName, getAvailableTransforms]);
478
+ const availableTransforms = React.useMemo(() => getAvailableTransforms(parsedCode ?? loadedCode, variantName), [parsedCode, loadedCode, variantName]);
345
479
 
346
- // Effect to compute transformations for all variants
480
+ // Effect to compute transformations for all variants. Only runs when the
481
+ // full async pipeline is wired (`parsedCode` + worker + deltas computer);
482
+ // the no-async case is derived during render below instead of being stored,
483
+ // so this effect never publishes a synchronous pass-through state.
347
484
  React.useEffect(() => {
348
- if (!parsedCode || !sourceParser || !computeHastDeltas) {
349
- setTransformedCode(parsedCode);
485
+ if (!parsedCode || !sourceParser || !computeHastDeltasLoader) {
350
486
  return;
351
487
  }
352
488
 
353
489
  // Process transformations for all variants
354
490
  (async () => {
355
491
  try {
356
- const parseSource = await sourceParser;
492
+ // Resolve the parser and the (lazy) transform-delta computer in parallel
493
+ // before computing deltas. computeHastDeltas pulls jsondiffpatch, so it's
494
+ // kept out of the initial bundle under CodeProviderLazy.
495
+ const [parseSource, computeHastDeltas] = await Promise.all([sourceParser, computeHastDeltasLoader()]);
357
496
  const enhanced = await computeHastDeltas(parsedCode, parseSource);
358
- setTransformedCode(enhanced);
497
+ setTransformedState({
498
+ input: parsedCode,
499
+ output: enhanced
500
+ });
359
501
  } catch (error) {
360
502
  console.error(new Errors.ErrorCodeHighlighterClientTransformProcessingFailure(error));
361
- setTransformedCode(parsedCode);
503
+ setTransformedState({
504
+ input: parsedCode,
505
+ output: parsedCode
506
+ });
362
507
  }
363
508
  })();
364
- }, [parsedCode, sourceParser, computeHastDeltas]);
509
+ }, [parsedCode, sourceParser, computeHastDeltasLoader]);
510
+
511
+ // When the full async pipeline is wired, expose the cached output regardless
512
+ // of whether `parsedCode` changed since the last computation — falling back
513
+ // to `undefined` here would yank the currently-displayed HAST for a frame
514
+ // while the async pipeline catches up. Staleness is signalled via
515
+ // `waitingForTransformedCode` so downstream gates (e.g.
516
+ // `useTransformManagement` / `useVariantSelection`) hold off committing a
517
+ // swap until fresh deltas land. Without the pipeline, `transformedCode` is a
518
+ // synchronous pass-through of `parsedCode` derived during render.
519
+ const hasAsyncPipeline = !!parsedCode && !!sourceParser && !!computeHastDeltasLoader;
520
+ const transformedCode = hasAsyncPipeline ? transformedState.output : parsedCode;
521
+
522
+ // Async hast-deltas pipeline status. While true, consumers (notably
523
+ // `useTransformManagement`'s `deferHighlight` gate) should treat
524
+ // highlighting as not-yet-settled and hold off committing a transform
525
+ // swap. Without this, the swap can commit after `parsedCode` is ready
526
+ // but *before* `computeHastDeltas` resolves: the incoming tree first
527
+ // renders without the transform deltas, then re-renders a frame or
528
+ // two later when `transformedCode` arrives, producing a visible jump
529
+ // on top of the just-played collapse animation.
530
+ //
531
+ // Only relevant when both a worker (`sourceParser`) and a deltas
532
+ // computer (`computeHastDeltas`) are wired up — environments without
533
+ // them resolve `transformedCode` synchronously to `parsedCode` in the
534
+ // effect above, so the deltas phase is a no-op. We compare the cached
535
+ // `input` against the live `parsedCode` instead of just checking
536
+ // `!transformedCode` so a freshly-arriving variant re-engages the wait
537
+ // until its deltas land.
538
+ const waitingForTransformedCode = hasAsyncPipeline && transformedState.input !== parsedCode;
365
539
  return {
366
540
  transformedCode,
367
- availableTransforms
541
+ availableTransforms,
542
+ waitingForTransformedCode
368
543
  };
369
544
  }
370
545
  function useControlledCodeParsing({
@@ -374,6 +549,7 @@ function useControlledCodeParsing({
374
549
  preParsedCache
375
550
  }) {
376
551
  const {
552
+ sourceParser,
377
553
  parseSource,
378
554
  parseControlledCode
379
555
  } = useCodeContext();
@@ -383,26 +559,30 @@ function useControlledCodeParsing({
383
559
  if (!code) {
384
560
  return undefined;
385
561
  }
386
- if (!parseSource || !parseControlledCode) {
387
- // Log when provider functions are missing to help with debugging
388
- if (!parseSource) {
389
- if (forceClient) {
390
- console.error(new Errors.ErrorCodeHighlighterClientMissingParseSource(url, true));
391
- } else {
392
- console.error(new Errors.ErrorCodeHighlighterClientMissingParseSource(url, false));
393
- }
562
+ if (!parseSource) {
563
+ // A CodeProvider is present and its async `sourceParser` promise hasn't
564
+ // resolved yet (e.g. CodeProviderLazy dynamic-importing the engine) — wait
565
+ // for it instead of erroring. The memo re-runs once `parseSource` lands.
566
+ if (sourceParser) {
567
+ return undefined;
394
568
  }
395
- if (!parseControlledCode) {
396
- if (forceClient) {
397
- console.error(new Errors.ErrorCodeHighlighterClientMissingParseControlledCode(url, true));
398
- } else {
399
- console.error(new Errors.ErrorCodeHighlighterClientMissingParseControlledCode(url, false));
400
- }
569
+ if (forceClient) {
570
+ console.error(new Errors.ErrorCodeHighlighterClientMissingParseSource(url, true));
571
+ } else {
572
+ console.error(new Errors.ErrorCodeHighlighterClientMissingParseSource(url, false));
573
+ }
574
+ return undefined;
575
+ }
576
+ if (!parseControlledCode) {
577
+ if (forceClient) {
578
+ console.error(new Errors.ErrorCodeHighlighterClientMissingParseControlledCode(url, true));
579
+ } else {
580
+ console.error(new Errors.ErrorCodeHighlighterClientMissingParseControlledCode(url, false));
401
581
  }
402
582
  return undefined;
403
583
  }
404
584
  return parseControlledCode(code, parseSource, preParsedCache);
405
- }, [code, parseSource, parseControlledCode, forceClient, url, preParsedCache]);
585
+ }, [code, sourceParser, parseSource, parseControlledCode, forceClient, url, preParsedCache]);
406
586
  return {
407
587
  parsedControlledCode
408
588
  };
@@ -420,7 +600,7 @@ function useGlobalsCodeMerging({
420
600
  loadCodeMeta,
421
601
  loadSource,
422
602
  loadVariantMeta,
423
- loadIsomorphicCodeVariant
603
+ loadIsomorphicCodeVariantLoader
424
604
  } = useCodeContext();
425
605
 
426
606
  // Set processedGlobalsCode if we have ready Code objects but haven't stored them yet
@@ -440,7 +620,7 @@ function useGlobalsCodeMerging({
440
620
  }
441
621
  // If not all ready, fall through to loading logic below
442
622
  }
443
- if (!loadIsomorphicCodeVariant) {
623
+ if (!loadIsomorphicCodeVariantLoader) {
444
624
  console.error(new Errors.ErrorCodeHighlighterClientMissingLoadVariantForGlobals());
445
625
  return;
446
626
  }
@@ -448,6 +628,8 @@ function useGlobalsCodeMerging({
448
628
  // Need to load string URLs or load missing variants
449
629
  (async () => {
450
630
  try {
631
+ const loadIsomorphicCodeVariant = await loadIsomorphicCodeVariantLoader();
632
+
451
633
  // First, load any string URLs into Code objects
452
634
  const basicCodeObjects = await Promise.all(globalsCode.map(async item => {
453
635
  if (typeof item === 'string') {
@@ -509,7 +691,7 @@ function useGlobalsCodeMerging({
509
691
  console.error(new Errors.ErrorCodeHighlighterClientLoadGlobalsCodeFailure(url || 'No URL', error));
510
692
  }
511
693
  })();
512
- }, [url, globalsCode, processedGlobalsCode, setProcessedGlobalsCode, loadCodeMeta, loadSource, loadVariantMeta, variants, loadIsomorphicCodeVariant]);
694
+ }, [url, globalsCode, processedGlobalsCode, setProcessedGlobalsCode, loadCodeMeta, loadSource, loadVariantMeta, variants, loadIsomorphicCodeVariantLoader]);
513
695
 
514
696
  // Determine globalsCodeObjects to use (prefer processed, fallback to direct if ready)
515
697
  const globalsCodeObjects = React.useMemo(() => {
@@ -657,15 +839,23 @@ export function CodeHighlighterClient(props) {
657
839
  const isControlled = Boolean(props.code || controlled?.code);
658
840
  const [code, setCode] = React.useState(typeof props.precompute === 'object' ? props.precompute : undefined);
659
841
 
660
- // Sync code state with precompute prop changes (for hot-reload)
661
- React.useEffect(() => {
842
+ // Sync code state with precompute prop changes (for hot-reload). Done with
843
+ // the store-previous-prop render-phase derivation rather than an effect:
844
+ // `code` is genuinely state (also mutated by `useInitialData` via `setCode`
845
+ // for client fallback loading) so it can't be pure derivation, but the
846
+ // re-seed on a new `precompute` is a render-time setState off the previous
847
+ // prop value. Match the original effect's branch logic: only object values
848
+ // re-seed and only an explicit `undefined` clears — any other value (e.g. a
849
+ // loader) leaves `code` untouched.
850
+ const [prevPrecompute, setPrevPrecompute] = React.useState(props.precompute);
851
+ if (props.precompute !== prevPrecompute) {
852
+ setPrevPrecompute(props.precompute);
662
853
  if (typeof props.precompute === 'object') {
663
854
  setCode(props.precompute);
664
855
  } else if (props.precompute === undefined) {
665
- // Only reset to undefined if precompute is explicitly undefined
666
856
  setCode(undefined);
667
857
  }
668
- }, [props.precompute]);
858
+ }
669
859
 
670
860
  // State to store processed globalsCode to avoid duplicate loading
671
861
  const [processedGlobalsCode, setProcessedGlobalsCode] = React.useState(undefined);
@@ -689,8 +879,112 @@ export function CodeHighlighterClient(props) {
689
879
  highlightAfter,
690
880
  enhanceAfter,
691
881
  fallbackUsesExtraFiles,
692
- fallbackUsesAllVariants
882
+ fallbackUsesAllVariants,
883
+ editActivation
693
884
  } = props;
885
+
886
+ // Speculative preload: on first render, start fetching the heavy loaders this
887
+ // block is about to need (under CodeProviderLazy) so they're in flight before
888
+ // the content mounts and awaits them. Signals are cheap + accurate, so a
889
+ // precomputed or code-free block preloads nothing.
890
+ // Only the precomputed/loaded (non-controlled) code drives speculative loading.
891
+ const speculativeCode = isControlled ? undefined : code;
892
+ const speculativeGrammarScopes = React.useMemo(() => speculativeCode ? detectGrammarScopes(speculativeCode) : [], [speculativeCode]);
893
+ const speculativeAllPresent = React.useMemo(() => speculativeCode ? hasAllVariants(variants, speculativeCode) : false, [variants, speculativeCode]);
894
+ const speculativeHasTransforms = React.useMemo(() => !!speculativeCode && !hasAllVariants(variants, speculativeCode, true) && getAvailableTransforms(speculativeCode, variantName).length > 0, [variants, speculativeCode, variantName]);
895
+ useSpeculativeCodePreload({
896
+ needsData: !isControlled && !!url && !speculativeAllPresent,
897
+ hasTransforms: speculativeHasTransforms
898
+ });
899
+
900
+ // Per-block editing activation: flipped once when the block first engages for
901
+ // editing — threaded down to `useEditable.onActivate` via `CodeHighlighterContext`
902
+ // (immediately in `'eager'`, on hover/focus/click in `'interaction'`). Drives
903
+ // the editable speculative preload below and notifies the CodeControllerContext.
904
+ const [editingActivated, setEditingActivated] = React.useState(false);
905
+ const controllerOnActivate = controlled?.onActivate;
906
+ const handleEditingActivated = React.useCallback(() => {
907
+ setEditingActivated(true);
908
+ controllerOnActivate?.();
909
+ }, [controllerOnActivate]);
910
+
911
+ // Grammar scopes the editable files need for live re-highlighting. Unlike the
912
+ // speculative highlight/transform preloads — which intentionally skip
913
+ // controlled blocks (`speculativeCode` is cleared above) — an editable block
914
+ // DOES re-highlight its edits on the client, so its grammars must load or the
915
+ // edited source falls back to plain text. The editable file set (and thus the
916
+ // scopes) comes from `props.code`: editing changes source *content*, never
917
+ // which files exist, so this stays stable across keystrokes.
918
+ const editableGrammarScopes = React.useMemo(() => {
919
+ const editableCode = props.code ?? code;
920
+ return editableCode ? detectGrammarScopes(editableCode) : [];
921
+ }, [props.code, code]);
922
+
923
+ // When the block is editable (a CodeControllerContext with `setCode` is in
924
+ // scope), warm the live-editing engine, the per-language grammars, and the
925
+ // worker so they're in flight before the user edits. Deduped page-wide. In
926
+ // `editActivation: 'interaction'` mode the warming waits until the block is
927
+ // `activated` (engaged) — that mode defers loading until the reader engages.
928
+ useSpeculativeEditingPreload({
929
+ enabled: Boolean(controlled?.setCode),
930
+ editActivation,
931
+ activated: editingActivated,
932
+ scopes: editableGrammarScopes
933
+ });
934
+
935
+ // Preload the client-side transform applier (the `jsondiffpatch` chunk) when
936
+ // the code declares transforms — so it is warm before the reader switches a
937
+ // transform, in parallel with the (lazy) content. Broader than the
938
+ // `speculativeHasTransforms` highlight signal above: even a fully-precomputed
939
+ // (already-highlighted) block needs the applier to switch transforms
940
+ // client-side, so this drops the not-yet-highlighted gate. A block with no
941
+ // transforms never pulls the chunk.
942
+ const speculativeHasAnyTransforms = React.useMemo(() => speculativeCode ? getAvailableTransforms(speculativeCode, variantName).length > 0 : false, [speculativeCode, variantName]);
943
+ useSpeculativeUseCodePreload({
944
+ hasTransforms: speculativeHasAnyTransforms
945
+ });
946
+
947
+ // Preload the per-language grammar chunks this block needs, before `useCode`
948
+ // mounts and parses — in parallel with the (lazy) content. Only when the block
949
+ // will actually highlight client-side: it is forced client-side, not yet
950
+ // fully precomputed (so the client must parse), or eagerly editable (live
951
+ // re-highlight). A fully-precomputed read-only block renders its highlighted
952
+ // HAST and never parses, so it loads no grammar at all.
953
+ const willClientHighlight = !!speculativeCode && (Boolean(props.forceClient) || !hasAllVariants(variants, speculativeCode, true) || (editActivation ?? 'eager') !== 'interaction' && Boolean(controlled?.setCode));
954
+ useSpeculativeGrammarPreload({
955
+ scopes: speculativeGrammarScopes,
956
+ enabled: willClientHighlight
957
+ });
958
+
959
+ // ── Fallback hoisting ──
960
+ // State for fallbacks hoisted from ContentLoading via useCodeFallback.
961
+ // Content is stripped from Code on the server and passed to ContentLoading
962
+ // as source/extraSource props. ContentLoading hoists them back here so
963
+ // CodeHighlighterClient can derive text dictionaries for decompression.
964
+ const [hoistedFallbackHasts, setHoistedFallbackHasts] = React.useState({});
965
+
966
+ // Track whether ContentLoading called useCodeFallback via callback. The
967
+ // force-mount-once behavior (mounting the fallback even when the code is
968
+ // already ready, so `useCodeFallback` can hoist the DEFLATE dictionary) is now
969
+ // owned by `useCoordinatedSwap` below; this ref only drives the dev-time
970
+ // validation that ContentLoading wired its hoist hook.
971
+ const hookCalledRef = React.useRef(false);
972
+ const handleHookCalled = React.useCallback(() => {
973
+ hookCalledRef.current = true;
974
+ }, []);
975
+
976
+ // Stable callback for ContentLoading to hoist its fallbacks.
977
+ const handleSetFallbackHasts = React.useCallback((variant, hasts) => {
978
+ setHoistedFallbackHasts(prev => {
979
+ if (prev[variant] === hasts) {
980
+ return prev;
981
+ }
982
+ return {
983
+ ...prev,
984
+ [variant]: hasts
985
+ };
986
+ });
987
+ }, []);
694
988
  const {
695
989
  fallbackPending
696
990
  } = useInitialData({
@@ -705,9 +999,59 @@ export function CodeHighlighterClient(props) {
705
999
  fallbackUsesAllVariants,
706
1000
  isControlled,
707
1001
  globalsCode: props.globalsCode,
708
- setProcessedGlobalsCode
1002
+ setProcessedGlobalsCode,
1003
+ handleSetFallbackHasts
709
1004
  });
710
1005
 
1006
+ // Reverse the server-side residual consolidation, scattering the decompressed
1007
+ // fallbacks back onto the code so every variant carries its own dictionary
1008
+ // (the swap line-count classifier reads `code.fallback`, not the active-only
1009
+ // hoist). The blob is primed with the RENDERED subset's text, which reaches
1010
+ // the client only via the hoist — so wait for that subset to hoist before
1011
+ // decompressing. WHICH variants are rendered depends on `fallbackUsesAllVariants`
1012
+ // (every variant, or just the initial one); gate on THAT subset, never on the
1013
+ // *current* `variantName`, or swapping to a non-rendered variant drops the
1014
+ // scatter and strands the other variants without their dictionary.
1015
+ const residualFallbacks = props.residualFallbacks;
1016
+ const renderedVariant = props.initialVariant || props.defaultVariant || variants[0];
1017
+ const renderedHoisted = fallbackUsesAllVariants ? variants.every(variant => Boolean(hoistedFallbackHasts[variant])) : Boolean(hoistedFallbackHasts[renderedVariant]);
1018
+ const residualMap = React.useMemo(() => {
1019
+ if (!residualFallbacks || !renderedHoisted) {
1020
+ return undefined;
1021
+ }
1022
+ return decompressResidualFallbacks(residualFallbacks, residualDictionaryText(hoistedFallbackHasts));
1023
+ }, [residualFallbacks, renderedHoisted, hoistedFallbackHasts]);
1024
+
1025
+ // Scatter the dictionaries back onto whichever code carries it, so consumers
1026
+ // (the render and the swap line-count classifier) read `code.fallback` for any
1027
+ // variant. Two sources: the decompressed residual blob (`residualMap` — the
1028
+ // non-rendered variants, and under `fallbackUsesAllVariants` the blob is empty)
1029
+ // and the hoist (`hoistedFallbackHasts` — the rendered subset, which is the ONLY
1030
+ // place every variant's dictionary lives under `fallbackUsesAllVariants`). Skip
1031
+ // the hoist under `fallbackCollapsed`, where it is only each file's collapsed
1032
+ // window; the full dictionary comes from the blob there. Memoized so the
1033
+ // freshly-cloned code keeps a stable identity until its inputs change.
1034
+ const restoreFallbacks = React.useCallback(base => {
1035
+ if (!base) {
1036
+ return base;
1037
+ }
1038
+ let restored = residualMap ? scatterResidualFallbacks(base, residualMap) : base;
1039
+ if (!props.fallbackCollapsed) {
1040
+ // `preserveExisting`: never let the hoist overwrite a `fallback` already
1041
+ // on the variant. A fully-loaded `hastCompressed` source carries its own
1042
+ // source-paired (structured) `fallback`, which is the only valid DEFLATE
1043
+ // dictionary. The hoist can be an un-highlighted *raw-string* fallback
1044
+ // whose text keeps a trailing newline `buildRootFallback` drops, so
1045
+ // overwriting the structured one makes `decodeHastSource` throw a
1046
+ // dictionary mismatch. The hoist is the dictionary only when the variant's
1047
+ // own was stripped, so apply it solely where one isn't already present.
1048
+ restored = scatterResidualFallbacks(restored, hoistedFallbackHasts, true);
1049
+ }
1050
+ return restored;
1051
+ }, [residualMap, hoistedFallbackHasts, props.fallbackCollapsed]);
1052
+ const resolvedPropsCode = React.useMemo(() => restoreFallbacks(props.code), [props.code, restoreFallbacks]);
1053
+ const resolvedStateCode = React.useMemo(() => restoreFallbacks(code), [code, restoreFallbacks]);
1054
+
711
1055
  // Use useSyncExternalStore to detect hydration
712
1056
  const subscribe = React.useCallback(() => () => {}, []);
713
1057
  const getSnapshot = React.useCallback(() => true, []);
@@ -717,22 +1061,18 @@ export function CodeHighlighterClient(props) {
717
1061
  const [isEnhanceAllowed, setIsEnhanceAllowed] = React.useState(enhanceAfter === 'init' || enhanceAfter === 'hydration' && isHydrated);
718
1062
  React.useEffect(() => {
719
1063
  if (enhanceAfter === 'idle') {
720
- const requestIdleCallback = window.requestIdleCallback ?? setTimeout;
721
- const cancelIdleCallback = window.cancelIdleCallback ?? clearTimeout;
722
- const idleRequest = requestIdleCallback(() => {
723
- setIsEnhanceAllowed(true);
724
- });
725
- return () => cancelIdleCallback(idleRequest);
1064
+ return requestIdle(() => setIsEnhanceAllowed(true));
726
1065
  }
727
1066
  return undefined;
728
1067
  }, [enhanceAfter]);
729
1068
 
730
- // Update enhance allowed state when hydration completes
1069
+ // Enhance instantly once hydrated, as a non-blocking client transition,
1070
+ // rather than deferring to a scheduled task. (`enhanceAfter: 'idle'` above is
1071
+ // the mode that deliberately keeps the un-enhanced first paint and swaps in
1072
+ // the enhanced tree on a later idle render.)
731
1073
  React.useEffect(() => {
732
1074
  if (enhanceAfter === 'hydration' && isHydrated) {
733
- // we should ensure that each code highlighter is enhanced as a separate task
734
- // this should run from top to bottom
735
- yieldToMain().then(() => setIsEnhanceAllowed(true));
1075
+ React.startTransition(() => setIsEnhanceAllowed(true));
736
1076
  }
737
1077
  }, [enhanceAfter, isHydrated]);
738
1078
  const readyForContent = React.useMemo(() => {
@@ -757,7 +1097,9 @@ export function CodeHighlighterClient(props) {
757
1097
  const regularCode = props.code || code;
758
1098
  return regularCode ? hasAllVariants(variants, regularCode) : false;
759
1099
  }, [activeCode, isEnhanceAllowed, controlled?.code, variants, props.code, code]);
760
- useAllVariants({
1100
+ const {
1101
+ refresh: refreshAllVariants
1102
+ } = useAllVariants({
761
1103
  readyForContent,
762
1104
  variants,
763
1105
  isControlled,
@@ -773,7 +1115,7 @@ export function CodeHighlighterClient(props) {
773
1115
  // Merge globalsCode with internal state code (fetched data) - this should be stable once ready
774
1116
  const stateCodeWithGlobals = useGlobalsCodeMerging({
775
1117
  url,
776
- code,
1118
+ code: resolvedStateCode,
777
1119
  // Only use internal state, not props.code
778
1120
  globalsCode: props.globalsCode,
779
1121
  processedGlobalsCode,
@@ -784,7 +1126,7 @@ export function CodeHighlighterClient(props) {
784
1126
 
785
1127
  // For props.code (controlled), always re-merge when it changes (don't cache in state)
786
1128
  const propsCodeWithGlobals = usePropsCodeGlobalsMerging({
787
- code: props.code,
1129
+ code: resolvedPropsCode,
788
1130
  globalsCode: props.globalsCode,
789
1131
  processedGlobalsCode,
790
1132
  variants
@@ -794,7 +1136,8 @@ export function CodeHighlighterClient(props) {
794
1136
  const codeWithGlobals = propsCodeWithGlobals || stateCodeWithGlobals;
795
1137
  const {
796
1138
  parsedCode,
797
- deferHighlight
1139
+ deferHighlight: deferHighlightForParsing,
1140
+ highlightReady
798
1141
  } = useCodeParsing({
799
1142
  code: codeWithGlobals,
800
1143
  readyForContent: readyForContent || Boolean(props.code),
@@ -805,18 +1148,90 @@ export function CodeHighlighterClient(props) {
805
1148
  });
806
1149
  const {
807
1150
  transformedCode,
808
- availableTransforms
1151
+ availableTransforms,
1152
+ waitingForTransformedCode
809
1153
  } = useCodeTransforms({
810
1154
  parsedCode,
1155
+ loadedCode: codeWithGlobals,
811
1156
  variantName
812
1157
  });
813
1158
 
1159
+ // Combined highlight-readiness gate consumed via context (notably by
1160
+ // `useTransformManagement`). Stay deferred while either the sync
1161
+ // `parseCode` pass or the async `computeHastDeltas` pass is still in
1162
+ // flight — committing a transform swap with `transformedCode` still
1163
+ // pending causes the incoming pre to first render without the
1164
+ // transform deltas and then re-flow a frame or two later when the
1165
+ // deltas land, producing a visible jump on top of the collapse
1166
+ // animation. The wait only matters for highlighters with at least one
1167
+ // applicable transform; plain (variant-only) highlighters skip it so
1168
+ // their stored-preference resolution doesn't pay the deltas latency.
1169
+ const deferHighlight = deferHighlightForParsing || availableTransforms.length > 0 && waitingForTransformedCode;
1170
+
1171
+ // The fallback↔content swap, generalized into `useCoordinatedSwap`: it owns
1172
+ // the force-mount-once behavior, nested-fallback suppression (via the shared
1173
+ // `CoordinatedFallbackContext`), and registration with the page-wide settle
1174
+ // gate. `holdGate={deferHighlight}` keeps the gate open while the content
1175
+ // stays rendered (the highlighter shows plain text, then highlights in place)
1176
+ // rather than re-showing the fallback. CH keeps its own hoist state, residual
1177
+ // decompression, and `CodeHighlighterContext`/`CodeHighlighterFallbackContext`.
1178
+ const {
1179
+ showFallback,
1180
+ fallbackContext: coordinatedFallbackContext,
1181
+ hoisted
1182
+ } = useCoordinatedSwap({
1183
+ ready: activeCodeReady,
1184
+ holdGate: deferHighlight,
1185
+ hasFallback: !!props.fallback,
1186
+ skipFallback: props.skipFallback
1187
+ });
1188
+
1189
+ // Validate that ContentLoading calls useCodeFallback(props). Child effects
1190
+ // fire before parent effects, so hookCalledRef is set by the time this runs.
1191
+ React.useEffect(() => {
1192
+ if (showFallback && !hookCalledRef.current) {
1193
+ throw new Errors.ErrorCodeHighlighterClientMissingFallbackHoist();
1194
+ }
1195
+ }, [showFallback]);
1196
+
1197
+ // A dynamically-imported content (e.g. `LazyContent`) calls `reportReady` — via
1198
+ // the `CoordinatedContentContext` provided around `children` below — once its
1199
+ // `import()` resolves. Without a `ContentLoading` there is nothing to cover that
1200
+ // load (the slot would flash empty), so fail fast instead.
1201
+ const fallbackProvided = !!props.fallback;
1202
+ const reportContentReady = React.useCallback(() => {
1203
+ if (!fallbackProvided) {
1204
+ throw new Errors.ErrorCodeHighlighterClientDynamicContentRequiresFallback();
1205
+ }
1206
+ }, [fallbackProvided]);
1207
+
1208
+ // Hand the loading `fallback` down to the content so a dynamically-imported
1209
+ // content (`LazyContent`) shows the *same* `ContentLoading` as its Suspense
1210
+ // fallback while its chunk loads - the placeholder the swap showed keeps
1211
+ // covering the load, with no empty flash and no double render.
1212
+ const contentContext = React.useMemo(() => ({
1213
+ hoisted,
1214
+ reportReady: reportContentReady,
1215
+ fallback: props.fallback
1216
+ }), [hoisted, reportContentReady, props.fallback]);
1217
+
814
1218
  // Per-highlighter pre-parsed HAST cache. Lives in a ref so the same Map
815
1219
  // instance is shared across renders without becoming a React dep. The
816
1220
  // editable populates it via `useSourceEditing` (which reads it from
817
1221
  // `CodeHighlighterContext`), and `parseControlledCode` consults it on
818
1222
  // every render to skip the sync main-thread parse on exact source matches.
819
1223
  const [preParsedCache] = React.useState(() => new Map());
1224
+
1225
+ // Client-side refresh: re-run the FULL variant loader (via the chunk's
1226
+ // `refresh()`) and swap in fresh data, keeping the current highlighted output
1227
+ // visible until the new tree lands (stale-while-revalidate, via the existing
1228
+ // `deferHighlight` gate). Invalidate the per-file pre-parsed HAST cache so the
1229
+ // refreshed source re-parses instead of reusing stale entries. A no-op for a
1230
+ // block with no `url` to re-fetch from.
1231
+ const refresh = React.useCallback(() => {
1232
+ preParsedCache.clear();
1233
+ refreshAllVariants();
1234
+ }, [preParsedCache, refreshAllVariants]);
820
1235
  const {
821
1236
  parsedControlledCode
822
1237
  } = useControlledCodeParsing({
@@ -830,8 +1245,29 @@ export function CodeHighlighterClient(props) {
830
1245
  const overlaidCode = parsedControlledCode || transformedCode || codeWithGlobals;
831
1246
 
832
1247
  // For fallback context, use the processed code or fall back to non-controlled code
833
- const codeForFallback = overlaidCode || (controlled?.code ? undefined : props.code || code);
834
- const fallbackContext = React.useMemo(() => activeCodeReady ? undefined : codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants), [activeCodeReady, variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants]);
1248
+ const codeForFallback = overlaidCode || (controlled?.code ? undefined : resolvedPropsCode || resolvedStateCode);
1249
+
1250
+ // Resolve the active variant's fallbacks from the two places one can cross
1251
+ // the server→client boundary: the hoisted copy (from a `ContentLoading`
1252
+ // component, which had it stripped off `Code`) and the variant's own
1253
+ // `fallback` field on `Code` (present without a `ContentLoading`, or scattered
1254
+ // back from the residual blob). For most files only one is populated. When
1255
+ // both are — a `fallbackCollapsed` block hoists the *visible* window but
1256
+ // scatters the *full* fallback onto `Code` — the `Code` copy must win, since
1257
+ // the full text is the DEFLATE dictionary `hastCompressed` needs. So merge
1258
+ // with the derived (`Code`) copy taking precedence.
1259
+ const activeFallbacks = React.useMemo(() => {
1260
+ const merged = {
1261
+ ...hoistedFallbackHasts[variantName],
1262
+ ...deriveFallbacksFromCode(codeForFallback, variantName)
1263
+ };
1264
+ return Object.keys(merged).length > 0 ? merged : undefined;
1265
+ }, [hoistedFallbackHasts, variantName, codeForFallback]);
1266
+ const fallbackContext = React.useMemo(() => ({
1267
+ extraVariants: codeToFallbackProps(variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants).extraVariants,
1268
+ setFallbackHasts: handleSetFallbackHasts,
1269
+ onHookCalled: handleHookCalled
1270
+ }), [variantName, codeForFallback, fileName, props.fallbackUsesExtraFiles, props.fallbackUsesAllVariants, handleSetFallbackHasts, handleHookCalled]);
835
1271
  const context = React.useMemo(() => ({
836
1272
  code: overlaidCode,
837
1273
  // Use processed/transformed code
@@ -839,23 +1275,56 @@ export function CodeHighlighterClient(props) {
839
1275
  selection: controlled?.selection || selection,
840
1276
  setSelection: controlled?.setSelection || setSelection,
841
1277
  components: controlled?.components || props.components,
842
- availableTransforms: isControlled ? [] : availableTransforms,
1278
+ // Only suppress when an external CodeController owns the code; static
1279
+ // `props.code` still needs the locally-computed list.
1280
+ availableTransforms: controlled?.code ? [] : availableTransforms,
843
1281
  url: props.url,
844
1282
  deferHighlight,
1283
+ fallbacks: activeFallbacks,
1284
+ highlightReady,
1285
+ highlightAfter,
1286
+ editActivation,
1287
+ onEditingActivated: handleEditingActivated,
1288
+ refresh,
845
1289
  preParsedCache
846
- }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, isControlled, availableTransforms, props.url, deferHighlight, preParsedCache]);
1290
+ }), [overlaidCode, controlled?.setCode, selection, controlled?.selection, controlled?.setSelection, controlled?.components, props.components, controlled?.code, availableTransforms, props.url, deferHighlight, activeFallbacks, highlightReady, highlightAfter, editActivation, handleEditingActivated, refresh, preParsedCache]);
847
1291
  if (!props.variants && !props.components && !activeCode) {
848
1292
  throw new Errors.ErrorCodeHighlighterClientMissingData();
849
1293
  }
850
- const fallback = props.fallback;
851
- if (fallback && !props.skipFallback && !activeCodeReady) {
852
- return /*#__PURE__*/_jsx(CodeHighlighterFallbackContext.Provider, {
853
- value: fallbackContext,
854
- children: fallback
855
- });
1294
+
1295
+ // Reset (while the fallback shows) so the validation effect re-checks that
1296
+ // ContentLoading wired the hook; the child's `useCodeFallback` effect sets it
1297
+ // again before that runs.
1298
+ if (showFallback) {
1299
+ // eslint-disable-next-line react-hooks/refs -- dev-only validation flag; reset during render so the child useCodeFallback effect (fires before this parent's validation effect) can re-set it each time the fallback shows
1300
+ hookCalledRef.current = false;
856
1301
  }
857
- return /*#__PURE__*/_jsx(CodeHighlighterContext.Provider, {
1302
+
1303
+ // Provide the generic `CoordinatedFallbackContext` (so a nested CodeHighlighter
1304
+ // detects it via `useCoordinatedSwap` and suppresses its own swap, collapsing
1305
+ // the fallback→content→fallback→content flicker) alongside the CH-specific
1306
+ // fallback context that `useCodeFallback` reads to hoist.
1307
+ const fallbackNode = /*#__PURE__*/_jsx(CoordinatedFallbackContext.Provider, {
1308
+ value: coordinatedFallbackContext,
1309
+ children: /*#__PURE__*/_jsx(CodeHighlighterFallbackContext.Provider, {
1310
+ value: fallbackContext,
1311
+ children: props.fallback
1312
+ })
1313
+ });
1314
+
1315
+ // The content subtree. A dynamically-imported content (`LazyContent`) reads the
1316
+ // loading `fallback` from `CoordinatedContentContext` and shows it as its own
1317
+ // Suspense fallback while its `import()` resolves - so swapping to it never
1318
+ // flashes empty.
1319
+ const contentNode = /*#__PURE__*/_jsx(CodeHighlighterContext.Provider, {
858
1320
  value: context,
859
- children: props.children
1321
+ children: /*#__PURE__*/_jsx(CoordinatedContentContext.Provider, {
1322
+ value: contentContext,
1323
+ children: props.children
1324
+ })
860
1325
  });
1326
+
1327
+ // Show the fallback OR the content (swap on data-readiness). The content loads
1328
+ // its own chunk after the swap, covered by the fallback it inherits via context.
1329
+ return showFallback ? fallbackNode : contentNode;
861
1330
  }