-
Notifications
You must be signed in to change notification settings - Fork 1
WriteOnceComponents
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.
| 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 | — |
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.
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.
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 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).
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). -
DesignSystemTsGenerator→design-system.generated.ts(tokens, theme, Button size table). -
IconTsGenerator→icons.generated.ts(glyph path data from the C#IconRegistry).
See DesignSystem.
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.