Skip to content

WriteOnceComponents

Edgar Mesquita edited this page Aug 9, 2026 · 5 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.

This page is the architecture overview; the component catalog lives on the Components page, and how you WRITE a tree — factories rather than new, including for your own components — on Declarative Surface.

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 The write-once component library 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

Structure: Box (+BoxStyle — paint, opacity, transform, aspect-ratio, hover/focus diffs), Row/Column (gap-owned flex + Wrap/RunGap), Grid (+GridTrack — the CSS Grid twin), Stack/Positioned (z-order + ZIndex), AdaptiveNode (window size classes, zero listeners), ScrollView (+Sticky), Overlay (viewport layer, Modal flag), Anchored (floating panels: placements, outside-tap scrim, MatchAnchorWidth, OpenOnHover). Content: Text (type roles), TextEntry, Icon (curated + any pack glyph), Image, Spinner. Interaction & motion: Pressable (≥48dp hit contract), Link (real anchors on web, the host navigation seam on native), DragDismiss (the sheet gesture), LoopMotion, Presence (enter/exit motion). Plus value types (EdgeInsets, SizeValue Hug/Fill/Fixed, CornerRadii, ColorToken light/dark pairs, StyleDiff, Transform2D) and the component model (StatelessComponent, StatefulComponent + SetState, mode-free ComponentContext).

Styling has no CSS plane — see the atomic style engine in DesignSystem: every style declaration becomes one deduplicated atomic class, byte-identical between SSR (C#) and hydration (TS).

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.

One failure, one subtree

Every target expands a component through the SAME seam, and that seam is a boundary: if Build throws, the failing component's subtree is replaced by a contained surface and everything around it still renders. A page that has no parent to contain it carries the same boundary at its own render seam, so a broken page is a panel, never a blank document.

before now
Server (SSR) 500 for the whole request 200, panel in place of the subtree
Browser mount threw, root never written panel, siblings interactive
Window (Photon) frame never arrived panel, the app keeps presenting

The panel is built from the vocabulary, so it is the same surface on a page and in a window. In development it names the component and quotes the throw; in production it says only that a section could not be displayed — an exception message is written for whoever wrote the code, and can name an id, a path or a query. Either way the failure reaches the host's log through ComponentBoundary.Report: a boundary that only swallows trades a loud crash for a silent one.

Nothing is remembered. A component that stops throwing — a retry, new props, a hot reload — simply builds again on the next pass.

A component's own CONTRACT is not a render-time failure and does not belong here: state it where the mistake is written (init => field = value.Count > 3 ? throw … : value), so the author gets an exception at the line that got it wrong instead of a red panel once per frame.

The library is runtime-provided

The transpiled eQuantic.UI.Components modules ship inside runtime.js, byte-pinned in CI against the live eqc output — apps import them from @equantic/runtime. User-authored write-once components need no wiring: they live in the app and flow through the normal page scan (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.

The library — one library, written once

There is exactly ONE component library. Buttons, cards, inputs, navigation, overlays, lists, the recycling ListView, the Spreadsheet grid, the code editor — the full catalog, grouped and described, is on the Components page.

Each component ships with web realizer pins, native goldens (light+dark), pinned transpiled fixtures executed in vitest, and the live showroom (/ and /shared in DefaultUIDashboard — SSR + hydration identity + interaction verified end-to-end by the Playwright suite). Systems shipping alongside the library: the state-transition motion system (Presence enter/exit), the pointer pipeline (hover, drag-to-dismiss with slop/cancel/glide), the scroll compositor (real Sticky pinning) and anchored overlays.

Clone this wiki locally