44import types
55from pathlib import Path
66
7+ import reflex as rx
78from reflex_base .constants .colors import ColorType
89from reflex_docgen .markdown import (
910 Block ,
5253)
5354from reflex_ui_shared .constants import REFLEX_ASSETS_CDN
5455
55- import reflex as rx
56-
5756# ---------------------------------------------------------------------------
58- # Exec environment — mirrors flexdown 's module-based exec mechanism
57+ # Exec environment — mirrors reflex_docgen 's module-based exec mechanism
5958# ---------------------------------------------------------------------------
6059
6160# One in-memory module per file — all exec blocks within a doc accumulate
6261# into the same namespace, so later definitions shadow earlier ones cleanly.
6362_file_modules : dict [str , types .ModuleType ] = {}
63+ _executed_blocks : set [tuple [str , str ]] = set ()
6464
6565# Register the parent package so pickle can resolve child modules.
6666_PARENT_PKG = "_docgen_exec"
@@ -83,8 +83,16 @@ def _exec_code(content: str, env: dict, filename: str) -> None:
8383 """Execute a ``python exec`` code block via an in-memory module.
8484
8585 All exec blocks within the same file share one module so that State
86- subclass redefinitions shadow correctly.
86+ subclass redefinitions shadow correctly. When the same block is
87+ encountered a second time (e.g. the frontend is evaluated twice —
88+ once for compilation and once on the backend), skip re-execution and
89+ just populate *env* from the cached module namespace.
8790 """
91+ key = (filename , content )
92+ if key in _executed_blocks :
93+ env .update (_file_modules [filename ].__dict__ )
94+ return
95+
8896 if filename not in _file_modules :
8997 mod_name = _make_module_name (filename )
9098 module = types .ModuleType (mod_name )
@@ -99,6 +107,7 @@ def _exec_code(content: str, env: dict, filename: str) -> None:
99107 exec (compile (content , filename or "<docgen-exec>" , "exec" ), module .__dict__ )
100108
101109 env .update (module .__dict__ )
110+ _executed_blocks .add (key )
102111
103112
104113# ---------------------------------------------------------------------------
@@ -165,7 +174,7 @@ def _spans_to_plaintext(spans: tuple[Span, ...]) -> str:
165174class ReflexDocTransformer (DocumentTransformer [rx .Component ]):
166175 """Transforms a reflex_docgen Document into Reflex components.
167176
168- Mirrors the rendering that the flexdown pipeline produces, so docs from
177+ Mirrors the rendering that the reflex_docgen pipeline produces, so docs from
169178 the parent docs directory look identical to the locally-authored ones.
170179 """
171180
@@ -503,27 +512,34 @@ def title_comp() -> rx.Component:
503512 ),
504513 ]
505514
506- if children :
507- # Has body content — render as collapsible accordion.
508- if title_spans :
509- trigger .append (title_comp ())
510- body = rx .accordion .content (
511- self ._render_children (children ),
512- padding = "0px" ,
513- margin_top = "16px" ,
514- )
515- else :
516- trigger .append (
517- rx .box (
518- self ._render_children (children ),
519- class_name = "font-[475] !text-secondary-11" ,
520- ),
521- )
522- body = rx .fragment ()
515+ if children and title_spans :
516+ # Has heading + body — render as collapsible accordion.
517+ trigger .append (title_comp ())
518+ body = rx .accordion .content (
519+ self ._render_children (children ),
520+ padding = "0px" ,
521+ margin_top = "16px" ,
522+ )
523523 return collapsible_box (trigger , body , color )
524524
525- # Title only, no body — simple box.
526- trigger .append (title_comp ())
525+ # Title only, or text-only (no heading) — simple non-collapsible box.
526+ if title_spans :
527+ trigger .append (title_comp ())
528+ elif children :
529+ # Render inline spans directly — avoid text_block's mb-4 margin.
530+ spans : list [rx .Component | str ] = []
531+ for child in children :
532+ if isinstance (child , TextBlock ):
533+ spans .extend (_render_spans (child .children ))
534+ else :
535+ spans .append (self .transform_block (child ))
536+ trigger .append (
537+ rx .box (
538+ * spans ,
539+ class_name = "font-[475]" ,
540+ color = f"{ rx .color (color , 11 )} " ,
541+ ),
542+ )
527543 return rx .vstack (
528544 rx .hstack (
529545 * trigger ,
@@ -694,6 +710,13 @@ def render_docgen_document(
694710
695711
696712def get_docgen_toc (filepath : str | Path ) -> list [tuple [int , str ]]:
697- """Extract TOC headings as (level, text) tuples — same format as flexdown 's get_toc."""
713+ """Extract TOC headings as (level, text) tuples — same format as reflex_docgen 's get_toc."""
698714 doc = _parse_doc (filepath )
699715 return [(h .level , _spans_to_plaintext (h .children )) for h in doc .headings ]
716+
717+
718+ def render_markdown (text : str ) -> rx .Component :
719+ """Render a plain markdown text string into Reflex components."""
720+ doc = parse_document (text )
721+ transformer = ReflexDocTransformer ()
722+ return transformer .transform (doc )
0 commit comments