NE.Standard

Platforms

The framework is two halves. The core — everything under NE.Standard.UI.* except the Web packages — knows nothing about HTML, HTTP, SignalR or a browser: it compiles views, runs controllers, keeps the two in sync through a stream of updates, and hands a platform a description of what to draw. A platform renders that description, carries the updates to a client and the client's changes back, and implements the handful of services a controller reaches through its context. Web is the platform that ships today; this page is the contract a second one builds against.

What the core gives a platform

  • A compiled view. IUIHost.ResolveViewAsync answers a UIViewResolution: the route, the navigation request, the session, and a CompiledView — component nodes with their properties, the binding templates and indexes, the events, interactions, validation rules, item templates and dialogs, all addressed by id. A platform walks the nodes and draws each by its TypeKey; ids are stable for the life of the process, so what it drew can be addressed by every later update.
  • A runtime per client. IUIHost.AttachRuntimeAsync creates or finds the IUIRuntime behind a route with a controller, keyed by session, address and window (UIRuntimeKey, UIPersistenceOptions.Lifetime). The runtime answers the initial values (BuildInitialChangeSetAsync, BuildInitialCollectionChangesAsync), takes the client's writes (ProcessChangeSetAsync), runs commands (ProcessEventAsync), reads item windows (RequestItemWindowAsync) and flushes what the controller changed in the meantime (FlushAsync, driven by the core's own scheduler).
  • A stream of updates. Everything the client has to change arrives as a ServerChangeSet of ServerUIUpdates — a value, a collection change, a validation message, a full resync — each naming a component address and a property. A platform applies them to whatever it drew.
  • Client effects. A command or an interaction can raise a ClientEffect (focus, scroll, navigate, open a dialog, show a notification, copy to the clipboard, ...). ClientEffectKinds lists the built-in names; a platform runs the ones it implements and ignores the rest with a log line — a kind is a request, not a guarantee. DownloadFileEffect is the one shaped by the web — it names a path the browser fetches — and a platform whose download service saves the file itself never raises it.
  • The words the chrome needs. UIStrings names every word the framework draws that no author property sets — a picker's "Today", a notification's "Close" — with English text; the platform asks the ITranslator for the session's language and draws that.
  • The theme. UITheme is two UIColorPalettes, a UITypography, a UIShape and a focus-ring switch — values, not CSS. The web turns them into custom properties; another platform turns them into brushes.
  • Client-side rules. Filter and sort rules on a plain or virtualized items host run on the client. UIComparisonEvaluator in the core is the reference for what an operator answers — text comparison unless the operator is numeric, null reading as the empty string, a non-number comparing false under every numeric operator — and a platform's client follows it, or the two halves disagree about which row is visible.

What a platform implements

UIStartupBase refuses to start without these four; the web registers its own in WebStartupBase:

Service What it does
IUIUpdateSink Pushes a ServerChangeSet to every attached instance of a runtime, and a command's result to the one instance that raised it.
IUIDialogService Opens and closes a dialog declared by the view, for one connection.
IUIDownloadService Stages a stream or a byte array for the client to fetch as a file — StandardDownloadService, in Core, does the staging over IUIFileStore; the platform supplies only IUIDownloadAddressProvider.AddressOf(token), turning a staged token into the address the client fetches it from (the web: /_ne/files/{token}). An in-process platform can write the content straight into a save dialog instead and needs no address at all.
IUIUploadService Answers the files a FileInput selection carries, by selection id and file id, scoped to the session — StandardUploadService, also in Core, over IUIFileStore.

Optional, and worth having:

  • IUserSessionResolver — the shipped resolver loads the session the client presented and issues a new id when it presented none; a platform only replaces it to source identity differently. What the platform does own is carrying the id: the web puts it in an HttpOnly cookie named by UISessionOptions.ClientKey; an in-process platform holds it in memory.
  • IUIContentAddressResolver — where the platform serves content an IUIContentProvider answers by key (an avatar, a chat background); UIContentAddress is the default implementation, a fixed prefix the platform's startup constructs once at registration (new UIContentAddress(WebContentEndpoint.Prefix) on the web) and registers as the singleton. Reachable as UIApplication.Content and, for a controller, Context.Content. docs/FILES.md §8.
  • A theme-persistence helperSetThemeEffect both puts the client into a theme and reports the choice back to the session, so the next render agrees; the session write is UserSessionStoreExtensions.SetThemeModeAsync, a Core helper over IUserSessionStore a platform's theme-switch handler calls (the web: WebUIHub.SetThemeAsync).

And the glue that has no interface, because it is the platform's own shape:

  1. A connection is a UIInstance — a transport id, a WindowId and the navigation request — wrapped with the session into a UIHandle. A window is one top-level surface showing one address (a browser tab, a desktop window); one handle per attached client, and the runtime keeps every attached instance and the update sink sends to all of them.
  2. Resolve, then attach. A page load calls ResolveViewAsync (which runs the view filters and the route authorization) and then AttachRuntimeAsync; the runtime's IsInitialized says whether the controller's OnInitializeAsync has run. DetachRuntime when the connection goes; the core keeps the runtime for DisconnectedRetention so a reconnect finds it.
  3. The phase. ResolveViewAsync takes a UIViewRequestPhase: Open for the request that opens the view and can hand the client a new session id, Attach for a live connection attaching to it. The web calls both, once each per page load; a platform with one step passes Open — that is the phase in which a pending session-id rotation after SignInAsync is carried out, so a platform that never passes it never rotates.
  4. Two-way values. A platform decides which of its controls write back and sends a ClientChangeSet of ClientValueUIUpdates; the core accepts a value only for a property declared writable (UIBindingCapabilities.TargetToSource) and coerces it to the property's type (RecursiveValueCoercion).
  5. Items. A static list is rendered by the platform from the compiled template; a bound list arrives as collection inserts and is rendered from the same template on the client. Every item is keyed by its Id, and an address inside a template carries the keys of the enclosing rows as dynamic parameters — the walk that resolves a template against an item is ItemContext in the core (NE.Standard.UI.Compiled), which the web's renderers call and its client mirrors in binding-template-evaluator.ts, held to one shared corpus of cases so the two cannot drift.
  6. A dialog's own dismissal is a client-raised event. UIDialog.OnClose registers a command on the dialog's content component under the reserved event name close; a platform raises it only when the viewer dismisses the dialog (Escape, the backdrop) and never when the server closes it itself, or a controller cannot tell the two apart.
  7. A bound value written as a URL must be checked before it reaches the client. An href, an image src or a background url() built from a bound value is a javascript:/non-image-data: injection surface on both the render path and the live-patch path; the web checks both through one shared helper (WebUrlSafety) rather than trusting the render path and forgetting the patch path or vice versa. A second platform needs the equivalent check on whatever it treats as "safe to write as a link or an image source."
  8. Chrome a component's picker enforces is advisory only. FileInput.MaxFileSize (the web writes data-ui-file-max-size) and any similar picker-side limit are the client refusing early for a better experience; the real limit is still enforced wherever the bytes are received, since a client can simply not send the attribute a picker chrome reads.
  9. UITheme.PressRipple is a look a theme may switch off, not a mechanism a platform must implement. The web reads it at render (data-ui-press-ripple on the root) and answers it with a client engine; a platform that draws presses another way is free to ignore the flag.

Where the seam is drawn

  • Contracts/ and Core/ reference nothing above them and no web package; Web.Abstractions references Shell, never Core, so a render add-on does not drag the engine in.
  • Nothing in the core is internal that a platform needs: the web platform reaches the host through IUIHost and the application through UIApplication, both public. UIHost itself is internal on purpose.
  • The wire is JSON only where a wire exists. The converters on UIComponentId, UIProperty, ServerUIUpdate and ClientEffect live with the types so every channel agrees; an in-process platform never serializes and never touches them.