Skip to content

WriteOnceComponents

Edgar Mesquita edited this page Jul 5, 2026 · 6 revisions

Write-Once Components (Shared Architecture)

The core promise of eQuantic.UI's multi-target story: components are authored once, in C#, against an abstract vocabulary — and realized per target: DOM + CSS on the web, GPU pixels through Photon on native. Not "two similar APIs" — literally the same class.

Living plan: docs/SHARED-COMPONENTS-PLAN.md in the main repo (layering, styling rules, migration log). This page is the overview.

The layers

Package Role Depends on
eQuantic.UI.Primitives The shared foundation: colors, design tokens, typed styles, abstract nodes, component model — (zero)
eQuantic.UI.Components.Shared The write-once component library (Button, Card, …) Primitives only
eQuantic.UI.Web Web realizer: abstract tree → HtmlElement/DOM + CSS (SSR) + generated stylesheet Core, Primitives
eQuantic.UI.Native.Components Native realizer: abstract tree → Photon display list Primitives, Engine
TS runtime (@equantic/runtime) Client-side lowering (hydration) + embedded library modules

The abstract vocabulary (spec phase A)

Box (+BoxStyle), Row/Column (gap-owned flex), Stack/Positioned (z-order), Text (type roles), Icon (curated glyphs, §07 size whitelist), Image, Pressable (≥48dp hit contract), Flexible, Spacer — plus value types (EdgeInsets, SizeValue Hug/Fill/Fixed, CornerRadii, ColorToken light/dark pairs) and the component model (StatelessComponent, StatefulComponent + SetState, mode-free ComponentContext).

Layout parity is a contract: the C# flex engine (native) and CSS flex (web) implement the same spec — leftover-by-weight Flexible, the truncation contract (text shrinks to ellipsis before pushing siblings), stretch-fills-auto-only. Components author tokens, never resolved colors — one built tree realizes in light or dark.

How the same class reaches both targets

Native: PhotonHost expands Build() inline during layout and the realizer emits draw commands.

Web, server (SSR): the WebRealizer lowers the tree to HtmlElements. Colors become light-dark(#l, #d) so the DOM stays mode-free (theme switch = color-scheme). Core pages can embed write-once subtrees via the VisualNodeComponent bridge — and a Primitives StatefulComponent with [Page] is a full page (the server bridges it automatically).

Web, client: eqc (the C#→JS compiler) transpiles the same sources; the runtime's lowering (lowerVisualNode) mirrors the WebRealizer rule-for-rule — hydration parity is enforced by cross-pinned byte-exact style strings asserted in both the C# and TS test suites.

The library is runtime-provided

The transpiled Components.Shared modules ship inside runtime.js, byte-pinned in CI against the live eqc output. The compiler routes imports semantically by namespace:

using eQuantic.UI.Components;         // legacy web Button → per-app ./Button module
using eQuantic.UI.Components.Shared;  // write-once Button → import from "@equantic/runtime"

The deliberate name reuse between the two libraries never collides — the page's using directives decide. User-authored write-once components need no wiring: they live in the app and flow through the normal page scan (the eqc detects the Primitives component shapes, including direct-SetState stateful).

Generated, never hand-written

Every design-system value on the client is generated from the C# single source and byte-pinned:

  • PhotonCssGenerator → the normative stylesheet (custom properties, type-role classes, elevation, motion).
  • DesignSystemTsGeneratordesign-system.generated.ts (tokens, theme, Button size table).
  • IconTsGeneratoricons.generated.ts (glyph path data from the C# IconRegistry).

See DesignSystem.

Migration state (2026-07-05)

Write-once today (8): Button, Card, Divider, Badge (overlay ring), Chip, ProgressBar, Avatar (presence dot, person fallback), Banner (status icon). Each ships with web realizer pins, native goldens (light+dark), pinned transpiled fixtures executed in vitest, and a live sample page (/shared, /counter-shared in DefaultUIDashboard — SSR + hydration + interaction verified in a real browser).

Legacy web components (~54) migrate progressively; mixing is safe in both directions. Remaining gates before the next waves: ScrollView (engine clip), the interaction system (pressed/focus), the reconciler (positional state — removes the v1 state-hoisting pattern), and text input. End state: Components.Shared merges into eQuantic.UI.Components — one library, written once.

Clone this wiki locally