Phase 1: add lazy node tree (Node/Element/Safe/Fragment/Media)

Introduce a FastHTML-style component model alongside the existing
function-based one, purely additive:

- Node: base renderable; __html__/__str__ render lazily so str()/f-string
  composition keeps working during migration.
- Element: the single class for any HTML element (tag + attrs + children),
  rendering via the existing memoized _render_element.
- Safe: wraps pre-rendered HTML (migration bridge for f-string components).
- Fragment: ordered children with no wrapper tag (replaces str(a)+str(b)).
- BaseComponent: base for higher-level components; render() returns a
  subtree, media declared via a Media attribute.
- Media: declarative JS deps with order-preserving dedup merge.
- collect_media()/render() helpers walk the tree.

The legacy Component() function now builds an Element and is Node-aware in
its child handling, so a tree mixing string- and node-returning components
renders correctly with byte-identical output. No call sites changed yet.

https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
This commit is contained in:
Claude
2026-06-13 06:56:37 +00:00
parent e7db7eb0e8
commit f673f3ac80
3 changed files with 406 additions and 23 deletions
+16
View File
@@ -5,11 +5,19 @@ re-exports the public API so ``from common.components import X`` keeps working.
"""
from common.components.core import (
BaseComponent,
Component,
Element,
Fragment,
HTMLAttribute,
HTMLTag,
Media,
Node,
Safe,
_render_element,
collect_media,
randomid,
render,
)
from common.components.date_range_picker import (
DateRangeCalendar,
@@ -82,7 +90,15 @@ from common.utils import truncate
__all__ = [
"truncate",
"BaseComponent",
"Component",
"Element",
"Fragment",
"Media",
"Node",
"Safe",
"collect_media",
"render",
"HTMLAttribute",
"HTMLTag",
"_render_element",