-
Notifications
You must be signed in to change notification settings - Fork 2
Upgrading
🌐 This page in: English · Português
What a release CHANGED under you, version by version — the compile errors you will meet and the one-line answer to each.
Every entry here was met by a real app upgrading, not derived from a changelog. A break that cost somebody an hour and was never written down costs the next person the same hour.
Each entry opens with what got simpler, because a page that only lists breakage never says whether the upgrade was worth taking.
What got simpler. A component can be written over a base of your own, and a component the page
composes can load its own server data. Both were promised and neither held: a subclass of an app
base kept only its Build and died in the browser, and an IServerPrefetch below the page was never
called. The runtime also carries one committed copy of each transpiled module instead of two.
using eQuantic.UI.Primitives; // PhotonEntitlements, AppCategory… are no longer found here
using eQuantic.UI.Native.Hosting; // add thisPhotonCapabilityAttribute, PhotonEntitlementAttribute, PhotonEntitlements,
PhotonBundleKeyAttribute, PhotonBundleValueKind and AppCategory. A Photon app already
references the hosting assembly — it is where CreateApp comes from — so this is one using. A web
app could never name them usefully and is not affected.
Progress(child, value: new RangeValue(0.45f, 0, 1) { Text = "Estimating" }) // no longer compiles
Progress(child, value: new RangeValue(0.45f, 0, 1), valueText: "Estimating")The words moved to the node — Progress.ValueText and Adjustable.ValueText — because an
indeterminate bar has no number and lost its words along with it. Coming from .55, this is the
type's second change: .56 renamed AdjustableValue to RangeValue, and .57 moves Text off it.
var (role, path, bounds, label, value, disabled, isChecked, expanded,
current, selected, heading, live) = node; // no longer compiles
var (role, path, bounds, label, value, disabled, isChecked, expanded,
current, selected, heading, live, range) = node;Constructing is untouched: Range is a parameter with a default.
It was written by UseImageOptimization and read by nothing. Read ImageOptimizationOptions from
DI instead, which is what the /_equantic/image endpoint does on every request.
-
UI.Glyph(glyph)becomesUI.Icon(glyph).UI.Iconandnew Icontake anIconGlyph, with an implicit conversion fromIcons, soUI.Icon(Icons.Check)is unchanged. -
EmailRenderer.Render(component, theme)binds to theVisualNodeform, which a component already is, with the same parameters. -
WebRealizer.Lower(node, theme, scale, styles)is one method withStyleSink? styles = null. -
CodeWriter.BeginScope(line, opener, closer)is gone; the opener-and-closer form stays.
public abstract class CardBase : StatelessComponent
{
[ServerAction] public Task<string> Load() => …; // EQ2012
}The server registers an action under the CONCRETE component's name, so a stub on the base invoked an id nothing served. Declare the action on the concrete component.
Not a compile error, but a change in what happens: a component implementing IServerPrefetch below
the page used to be skipped, and now its PrefetchAsync runs and its fields reach the hydration
payload. A component that implemented it expecting nothing will find that something does.
What got simpler. No member survives to keep an old shape alive. TypeStyle and SemanticNode
carried a constructor and a Deconstruct added so an assembly compiled against an older package
would still bind, and both are gone with the paragraph that explained them. An extension over the
vocabulary lowers like every other extension, so node.Centered() is VisualNodeExtensions.centered(node)
and the two mirrors plus the import-cycle seam that exemption needed went with it. That also closes a
hole no C# author could see: a component parameter named like a runtime member used to overwrite it
and take the page down in the browser with centered is not a function.
Value = new AdjustableValue(now, min, max) // no longer compiles
Value = new RangeValue(now, min, max)The same three numbers, the same optional Text. The name moved because a second node carries the
trio now: Progress.Value is a RangeValue too, and a progress bar is not adjustable.
new WebFrame { Source = "https://example.com", Title = "Docs" } // no longer compiles
new WebFrame { Document = "<p>hello</p>" } // no longer compiles
WebFrame(WebContent.Url("https://example.com"), "Docs")
WebFrame(WebContent.Document("<p>hello</p>"), "Note")A frame shows a URL or inline markup and never both, which two init properties could not say. The
title is a constructor argument for the reason it exists at all: a frame nobody can name is one a
screen reader cannot announce.
new Navigable(rows, onMove) // no longer compiles
new Navigable(onMove, rows)Children go last, the way every container in the vocabulary takes them.
ChartSeries(name: "2026", values: [15, 21]) // no longer compiles
ChartSeries(Name: "2026", Values: [15, 21])A positional record's parameters ARE its properties, so new ChartSeries(Name: …) and the factory
take one spelling between them. Only a NAMED argument breaks, and CategoryAxis and ValueAxis
changed the same way.
var (size, line, weight, tracking, maxScale, mono, italic) = style; // no longer compiles
var (size, line, weight, tracking, maxScale, mono, italic, family) = style;Both carried a shorter Deconstruct (and TypeStyle a matching constructor) so code compiled
against an older package would still bind. Constructing is untouched, since the new parameters have
defaults. Only the deconstruction changes, and the compiler names the arity it wants.
var (bounds, destination) = region; // no longer compiles
var (bounds, destination, path) = region;A link region now knows the layout path it came from, which is what lets Tab reach it and activate
it on Photon. Only a host reading the realized regions is affected, and RealizeResult's constructor
grew for the same walk.
What got simpler. Every dispatch over the vocabulary is a visitor now: the layout engine, the
Photon paint pass, the web realizer, the server's DOM, the semantics walk and both email
alternatives. A node that a realizer forgot is a compile error instead of a default arm, which is
how Navigable and WebFrame were found measuring as a silent zero box. The twenty wrappers that
hold one child share a base, SingleChildNode, where four hand-kept lists used to disagree in eight
places. It shows on a native screen: a Text inside a Pressable, a Link or a Hoverable, in a
Row too narrow for it, now truncates like a bare Text instead of running 128dp past the row's
edge. And a slider says what it holds, on the web and to VoiceOver and TalkBack.
It was a view over Sizing, and the ladder is now the only statement of a control's metrics. Each
field of Metrics(size, density) was already produced by the rung it now names, so no number moves:
ButtonStyles.MinWidth // → Sizing.ButtonMinWidth
ButtonStyles.Metrics(size, density).Height // → Sizing.Height(size, density)
ButtonStyles.Metrics(size, density).PadX // → Sizing.PaddingX(size, density)
ButtonStyles.Metrics(size, density).Gap // → Sizing.Gap(size)
ButtonStyles.Metrics(size, density).LabelSize // → Sizing.LabelSize(size, density)
ButtonStyles.Metrics(size, density).IconSize // → Sizing.Icon(size)
ButtonStyles.Metrics(size, density).Radius // → Sizing.Radius(size)
ButtonStyles.Metrics(size, density).Hit // → Sizing.HitTarget(size, density)FlexNode container = row; // EQ2010 in a componentThe runtime never exported a twin for FlexNode, so a client component that named it compiled and
then died at hydration on "does not provide an export named". eqc now says so at the call site.
Name the concrete node, Row or Column, instead. Row(...).With(child) keeps compiling, because
the fence asks what a member was reached through. Server code and the native track are unchanged.
A GetService call with neither a type argument nor an argument used to emit getService(), which
the runtime cannot answer, because its registry is keyed by the interface NAME. Nothing reported
it. It is now EQ2111, the error the other path to the same situation already gave. If your build
stops here, that call was already returning nothing in the browser on .54: pass the type.
ARIA requires role="slider" to carry aria-valuenow, and every slider the SDK shipped carried
the role without the value. The role is derived from the value now. An Adjustable at its default
role is a slider when it has a Value and a group when it has none, which is what a focusable
container the arrow keys adjust is. A value on Tablist or Radiogroup, roles that carry none, is
not emitted.
Adjustable(track, onAdjust, value: new AdjustableValue(now, 0, 1000) { Text = "R$ 400" });
Slider(budget, onBudget, valueText: "R$ 400");The shipped Slider hands over the value its thumb is drawn from, and valueText gives words in
place of the number, because only the app knows what its numbers mean.
What got simpler. Geometry has one home. Rect, Point and Size were declared in the native
engine and copied four times to reach the places that needed them; they now live in
eQuantic.UI.Primitives beside the rest of the vocabulary, so a component, a chart and a realizer
all name the same type. A painter takes that box instead of four loose floats. And the node
hierarchy is closed, which means a realizer missing a case is a build error rather than a node that
silently fails to draw.
Correction to .53 first, because it shipped wrong. .53's entry said a truncated native label
"now ends in …". The mark was right and the PLACE was not: we passed CoreText the constant 2
believing it meant truncate-at-end, and 2 is kCTLineTruncationMiddle. So on macOS .53 cut
labels in the MIDDLE — "Documents and Settings" became "Docum…ttings". .54 puts the ellipsis at
the end, where the entry always claimed it was. Found by a consumer, not by us.
Rect, Point, Size, RRect and Matrix2D moved from eQuantic.UI.Native.Engine to
eQuantic.UI.Primitives. A source rebuild is the whole migration — both assemblies ship in the same
SDK at the same version — and only a consumer holding a compiled reference relinks. No type
forwarders: in preview a break is free, and a forwarder is a second name for the one type we just
finished removing four copies of.
The same move, same reason, for SemanticRole, SemanticCheck and SemanticNode, out of
eQuantic.UI.Native.Components.
var middle = a + b; // EQ2010 in a componentJavaScript cannot overload an operator, so a + b on two Points emitted JavaScript's own + and
concatenated two objects into "[object Object][object Object]"; a * 2 produced NaN. It did
this silently, which is why it is now a build error instead of a page that renders the wrong number.
On the server and in the native track they work as before. In a component, write the arithmetic out:
var middle = new Point(a.X + b.X, a.Y + b.Y);RRect and Matrix2D are fenced whole — they are what a rasterizer consumes, and a page naming
Matrix2D was emitting an import for a symbol the runtime does not export.
void FillRect(Rect box, ColorToken color, float cornerRadius = 0);
void FillCircle(Point center, float radius, ColorToken color);
void Line(Point from, Point to, ColorToken color, float strokeWidth);
Size Size { get; }Only an author of a CUSTOM painter changes anything. Code that draws on the painter we hand it is
untouched. The four floats were always the four a Rect is, spelled out at every call.
routeData() and the RouteData type are gone. RouteValues.from(params, query) and RouteValues
replace them, and getCurrentRoute() still exists and answers a RouteValues — one rename and one
import. Two behaviours changed with it:
-
paramandqueryanswernullwhere the TypeScript answeredundefined, because C#'sParamreturnsstring?.??is unaffected; an=== undefinedcomparison is not. - A repeated query key answers its FIRST value on both sides.
?tag=a&tag=breached a page as"a,b"from the server and"a"in the browser; now both say"a".
Five members went, not one: ServiceProvider, SetGlobalServiceProvider, SetScopedServiceProvider,
RegisterService<T> and TryGetService<T>. What remains is GetService<T>(), and it returns T?
where it used to return T.
context.RegisterService(new Clock()); // gone — register with the app's own container
var clock = context.TryGetService<IClock>(); // gone
var clock = context.GetService<IClock>(); // now T?, so the null check is yoursTryGetService never had a twin at all — the transpiler emitted context.tryGetService(...), a
method the runtime has never had, so any component calling it was already broken in the browser.
Navigator.Go(href: "/reports"); // no longer compiles
Navigator.Go("/reports"); // always fine
Navigator.Go(destination: "/reports");A positional call is untouched. Only a NAMED argument breaks, which is the kind of break a compiler reports clearly and a reader of release notes never expects.
public sealed record BarRect(int Category, int Series, Rect Box, bool Negative, bool DataEnd);It was eight positional parameters with X, Y, Width and Height spelled out. The generated
constructor, the Deconstruct and those four properties went with the change: read bar.Box.X
rather than bar.X, and construct with a Rect. Only code that reads the solved chart geometry
directly is affected — BarChart itself is untouched.
What got simpler. A brand code face works for the theme that has one — IAppTheme.MonoFamily
shipped in .51 and was inert for exactly the themes it was built for, so if you named the face at
every call site to work around that, you can stop. A bookmark link lands in the same place cold and
warm. A spreadsheet exists in the server's HTML. And the native layout engine carries no mutable
state, which changes nothing you write and a good deal of what anyone reading it has to hold.
Nothing breaks. No API moved, nothing was renamed, and the one source change is inside the native layout track whose only caller is the SDK itself.
MonoFamily did nothing for the normal way of branding type:
public TypeStyle Type(TypeRole role) => Base.Type(role) with { Family = "IBM Plex Sans" };Every role names a face, so the theme's code face always found Family already set and stepped
aside on the rule that a style which NAMES a family keeps it. Nothing about that family was chosen
for the text in question — it came from the ROLE. So the feature worked for a theme that leaves
Family null and was inert for one that does not, which is backwards.
What changes for you: if you named the code face at each call site to work around this, those lines are now redundant rather than wrong.
.52 fixed this and the fix did not work. The measurement that explains it is worth keeping:
cold, loadEventEnd 1310 ms → correct
warm, loadEventEnd 36 ms → wrong
The faster the page, the more reliably it failed. The .52 correction took its one chance at
load, and load can fire BEFORE the browser performs its fragment jump. A returning visitor is
almost everyone, and was always in the broken column.
It watches frames now, bounded by a budget once the document completes and by a wall clock whatever the page does — and it retires when the reader navigates, so it cannot follow them into another page.
Nothing to change in your code. A scroll nudge you added to work around this is now redundant.
SheetSurface had no arm in the web realizer, so SSR wrote an empty <span> where the browser
draws a grid. Since the day it shipped: a crawler never saw the data, a reader without scripts saw a
hole. The server now writes the grid, its child, role="grid", its name and a tabindex.
CodeSurface is deliberately not included yet — the client appends a caret to every editor, so a
server tree with only the child is one element short of what hydration expects, and that shape is a
decision still to take.
ITextMeasurer has promised a trailing ellipsis since it was written, and a cut line ended four
different ways. CoreText truncates in its own layout now, so the width reported includes the mark
and the glyphs drawn are that same line.
How you find it: a truncated label on macOS ends in … and measures a little wider than before,
because a truncation runs to the character where a wrap stops at the last whole word. If you keep
pixel goldens of your own over truncated native text, they move once. DirectWrite still cuts without
a mark.
Correction, and it is
0.2.0-preview.53that is wrong. That release cuts the MIDDLE out of a truncated label on macOS —Replace hardco…with IAppTheme— not the end. We passed CoreText's truncation constant2with a comment calling itkCTLineTruncationEnd, andCTLine.hsays 2 iskCTLineTruncationMiddle. So the mark is there, in the wrong place, and the web realizer beside it cuts at the end as the design system asks. Fixed after.53, with the assertion that was missing: two strings that share a head must cut to the same width. Only native text is affected — the web was always right.
Native layout only. LayoutContext no longer carries IndeterminateWidth, IndeterminateHeight,
StretchWidth or StretchHeight — those are facts about what a parent offered a child, so they
travel in the child's constraint. LayoutEngine.Layout gained an optional rootStretch.
Behaviour-neutral and measured that way: the pixel goldens are untouched and allocation is eight bytes per frame LOWER on both paths, because the context object is four fields smaller.
What got simpler. One thing you can stop working around: a bookmark link lands in the same place whether the reader followed it inside your app or pasted it into a fresh tab. If you had a scroll nudge of your own in front of that, it can go.
Nothing breaks. No API moved, nothing was renamed, and there is no behaviour here you have to go looking for — the one change is a fix you would have noticed as a reader.
Opening a link like /privacy#rights in a fresh tab left the target UNDER the sticky header, while
navigating to the same fragment from inside the app landed it correctly. That pairing is the whole
diagnosis: scroll-margin-top reading the measured chrome height was right, and only the ORDER was
wrong.
The chrome is measured after a render pass, so on a cold load the browser has already jumped with the height still unknown. A correction existed for exactly that and had one chance, which it spent on the MEASUREMENT rather than the correction — and because the browser re-runs its jump as late content settles the layout, that measurement often landed while the target was still far down the page. Nothing to correct, chance gone.
It now spends the chance on the correction, and books a second one on the document's own load,
re-measuring the chrome then rather than trusting the number that booked it — a bar that wraps, or
grows with a late webfont, is taller at load than at first paint.
How you find it: open a bookmark link into a long page in a fresh tab, with a sticky header on that page. The section heading used to sit behind the header; now it sits below it.
Nothing to change in your code. If you added a scroll nudge to work around this, it is redundant rather than wrong.
What got simpler. A brand typeface is one property on your theme (TypeStyle.Family per role,
IAppTheme.MonoFamily for code) instead of a StyleOverride at every call site, and a face the
machine lacks is NAMED on the run line instead of silently substituted. Text.Align works on Photon,
so centred text no longer needs a box around it. A laid-out node knows its Parent, so "where am I"
stops being a parsed path string. And two mysteries became build errors with remedies: one that
only ever appeared at hydration (EQ2010), and one that named a generated file you had never seen
(EQ3006, an adopting app that kept its own Main).
Nothing was renamed. One member changed shape. Five behaviours changed under code that still compiles, and a compiler cannot help you with those, so each says how to find it.
public IReadOnlyList<LayoutNode> Children { get; } // was List<LayoutNode>node.Children.Add(child) no longer compiles. Every laid-out node now carries Parent, recursively
to the root, and attaching is the engine's job so the link cannot be forgotten. Reading is unchanged,
and foreach (var child in node) — iterating the NODE, not the list — allocates nothing on the
per-frame paths.
A component calling FaceName.Usable(...) or reading FaceResolution.Unresolved used to compile,
emit, and die at hydration on "does not provide an export named" while SSR kept answering 200. It is
a build error now. Move the call to a [ServerAction], a [ServerOnly] class, or a realizer. The
fence is per SYMBOL, so FaceName.IsWellFormed still crosses while Usable does not.
PhotonOptions.Mode = null has documented "follows the system" since it existed, and the macOS shell
answered Light unconditionally — the one target of four that did not honour it.
How you find it: open your app on a Mac with dark mode on. If you were relying on the old
behaviour, say so with Mode = ThemeMode.Light rather than relying on a bug.
All three text services cut their raster tight to the longest line and drew every line at x=0, so
Text.Align had nowhere to go and was dropped.
How you find it: any Text that set Align and looked left-aligned on Photon now moves. A Row
or a hand-placed box you added to work around it will now double-centre.
The client's flat string catalogue was folded from the NEUTRAL culture alone, so pt-BR skipped
pt: a key translated once in Strings.pt.resx came back in ENGLISH on the client while the server
rendered it in Portuguese.
How you find it: a regional culture whose parent carries translations shows more of them now. Keys
you copied down into the regional .resx to work around this are now redundant rather than wrong.
The theme had both transparent with the ink in OnBase, and the pressed ink did not exist at all, so
the pressed-text swap Button defers had nothing to swap to. A custom theme or realizer reading
Colors(Variant.Link).Base now gets a colour. Nothing FILLS a Link, so the ink sits there without
painting a rectangle — what a Link never has is a fill, and that is the Subtle slot.
The face, the slant, the mono stack and a code role's white-space moved from inline styles into the
.eq-type-* class. The client's lowering cannot read the theme's type scale, so a role property
emitted inline by SSR was dropped on the first re-render — a hydration mismatch, not a cosmetic one.
How you find it: hand-written CSS overriding an inline font-family, font-style or
white-space on our text now competes with a class instead of an inline style, so it wins where it
used to lose. Entry controls take their face from .eq-entry.eq-type-* rather than an inline
font-family: inherit.
Nothing was renamed and nothing was removed. What this release changed is four behaviours under code that still compiles, and one package that is a different thing. A compiler helps you with the last one only, so each of the others says how to find it.
Handing a URL to the operating system hands it to whatever claims its scheme: https reaches a
browser, file: launches what the path names, and any scheme another app registered runs that app.
A link that arrived in content must not get to pick among those, so OpenUrl now consults one
policy first. It hands over http, https, and every scheme you already declared with
builder.Bundle.UrlScheme(…). Anything else is one line in Program.cs:
builder.Workspace.OpensMail(); // mailto:
builder.Workspace.OpensPhone().OpensMessages(); // tel:, sms:
builder.Workspace.Opens("x-apple.systempreferences"); // another app's scheme, or the system'sHow you find it: the call returns false and the realization logs a warning naming the scheme
and the exact line that would open it, so the first click tells you. It does not throw, because the
policy exists for URLs your app did not write. CanOpen applies the same policy, so a button gated
on it disappears for exactly the links OpenUrl refuses.
file: cannot be opted into. Use OpenFile or Reveal, which take a path, check it exists and
never launch a folder. And builder.Bundle.UrlScheme("acme://") now throws at its own line rather
than writing a manifest entry that matches nothing.
Key has been documented as reconciler identity since the vocabulary existed, and neither realizer
read it. Both read it now, which is a fix: a virtualized list that slides its window stops
renumbering its children, so the focus ring stops walking off the row it was on.
How you find it: it makes real a contract nothing was checking. Duplicate keys among siblings
used to be harmless because nobody read them. If you build a key from your own data, row.Key in a
DataTable being the shape to look for, confirm it is unique within its list.
Moving focus snaps the target into view. A glide had not arrived on the frame that draws the focus ring, so a keyboard walk could read the ring as off screen. A wheel or a drag still glides.
A stack now measures its children against its own box, where it was offering them the incoming
constraints, so a Fill child in a fixed-height stack measured zero under a scroll view. If you
pinned a height to work around that, the pin can come out. And a canvas with handlers keeps the
pointer, where a Stack's layers were switching pointer-events off and every hover fell through.
The only compile error here, and the one nobody is likely to meet: none of the three apps that track this SDK referenced that package. It used to be a small shared assembly for the two web-only wrappers; it is now the write-once chart library, and the three types moved into the wrappers that use them.
| If you used | It is now |
|---|---|
eQuantic.UI.Charts.IChart with the Chart.js wrapper |
eQuantic.UI.Charts.ChartJs.IChart — change the using
|
eQuantic.UI.Charts.IChart with the ApexCharts wrapper |
eQuantic.UI.Charts.ApexCharts.IChart — change the using
|
ChartData<T>, Dataset
|
Same name, same namespace; they ship inside eQuantic.UI.Charts.ChartJs now, which you already reference |
If you use the wrappers through their own packages, you have nothing to do. Only a project that
referenced eQuantic.UI.Charts directly for those types has to move.
eQuantic.UI.Core no longer exists. It was never one assembly: it was four unrelated things
sharing a name, and every web app installed all four to use some of them. Each part moved to its
only consumer.
| What | Where it lives now |
|---|---|
[Page], [ServerAction], [ServerOnly], [Authorize], [AllowAnonymous]
|
eQuantic.UI.Primitives, beside the other contract attributes |
HtmlElement, HtmlNode, HtmlStyle, DynamicElement, IComponent, RenderContext, ClassBuilder
|
eQuantic.UI.Web, where the DOM is realized |
SeoBuilder, MetadataCollection, AssetCollection, IRequireAssets
|
eQuantic.UI.Server.Metadata / eQuantic.UI.Server.Assets
|
ImageOptimizationState |
eQuantic.UI.Images |
Start by assuming the line is dead. On one site, 31 of 36 files that imported Core used nothing
from it at all. The instinct is to look up where each name went; for most files the answer is
nowhere. Delete the using, compile, and let the compiler name the few that need something.
For the ones that do, it is one of four:
- The file also imports
eQuantic.UI.Primitives→ delete the line. This was 45 of 51 files on one site, and 18 of 18 in the SDK's own samples. - The file uses only a contract attribute and does not import Primitives → change the line to
using eQuantic.UI.Primitives;. - The file uses a DOM type → change the line to
using eQuantic.UI.Web;. - The file imports a sub-namespace such as
eQuantic.UI.Core.Metadata→ the table above says where it went. These are the easy ones to miss, because they do not look like the line you grepped for.
A namespace can also live in a string. If your app generates, templates or compiles C# of its own, a playground or a code sample being the usual case, the compiler cannot see those. Grep the string literals too.
Three fixes you may have been working around. An anchor offset regression from .48 is corrected,
so remeasure any offsets you tuned against it. [ServerOnly] on one declaration no longer silences
the whole partial. And an AssemblyName ending in .App stops losing the executable.
The pre-write-once component model is gone. This is the largest deletion the project has made,
and the first release where "breaking" means something. If your app is written against the
write-once vocabulary — StatelessComponent/StatefulComponent from eQuantic.UI.Primitives,
VisualNode, the factories — nothing changes for you. The three samples build untouched.
What was removed, all of it from eQuantic.UI.Core:
| Gone | If you used it |
|---|---|
StatelessComponent / StatefulComponent built on HtmlElement, ComponentState, ComponentState<T>, InputComponent<T>
|
Move to the write-once model: state and lifecycle live on the COMPONENT, so a separate state class becomes fields plus OnMount — see Architecture for the shape |
IIconProvider |
There is no provider any more. A .svg in Assets/ becomes a Vectors.Mark, and a hand-written glyph is an IconGlyph property — see Icons
|
SvgElement |
HtmlElement("svg", …) on the web, or a Vectors.Mark for something that has to work on all three targets |
ComponentAttribute |
It marked nothing the compiler read. Delete it |
Styling/Colors, Styling/Animations, Styling/EQ
|
CSS values as strings, which the product principle rules out. Use ColorToken, Space, TypeRole and the transition channels — see Styling
|
The TypeScript half went with it: 260 lines of core/component.ts, and the ComponentState export.
A runtime bundle you have vendored by hand needs replacing rather than patching.
Also removed: the three unpackaged template trees under Templates/content/. Nobody could scaffold
them (dotnet new equantic-app has always used the write-once ones), but they were in the
repository teaching the wrong model to anyone who opened the folder.
Why now. The SDK is preview and every consumer is one of our own projects. Doing this after 1.0 would mean carrying two component models and a compiler that reads both, for the sake of code that nobody outside this repository ever wrote.
These bite an ordinary write-once app, unlike everything above. The rule behind them is one this project already had and had not finished applying: the abstract layer names what a thing IS, not what one target calls it.
| Was | Is | Why |
|---|---|---|
Sticky |
Pinned |
"Sticky" is CSS's word for it. A node that stays put while its container scrolls is pinned on every target |
ZIndex (on Positioned) |
Layer |
A z-index is a CSS coordinate. What the node declares is which layer it is in |
Alt (on Image) |
Label |
"Alt text" is HTML's name for an accessible name. The same string becomes an accessibilityLabel on the phones and a semantics label on Photon |
The compiler will not help you here: these are renames, so the old names simply do not resolve.
Sticky is the one to grep for first, because a pinned header is the shape almost every app has.
Nothing breaks. Said plainly because the previous release broke four things and the question is now reasonable. Everything below is new surface, and existing code compiles unchanged.
The one behaviour that CHANGES without a compile error: a macOS app's .app bundle now comes from
dotnet publish rather than from the build output, and it lands inside the publish directory. A
script that copied the bundle from bin/<config>/<tfm>/ after a publish is still copying the
development one — point it at bin/<config>/<tfm>/<rid>/publish/ instead.
If you turn on PublishTrimmed for the first time here, that is now supported and it was not
before: a trimmed Photon app used to open its window and ignore every setting, silently. Trim
warnings are zero on the sample; if you see one from your own code, it is yours.
Four things stop compiling. All four have a one-line fix, and none of them is a behaviour change in your own code.
Icons.Heart no longer converts to a node on its own, so a component that wants a CHILD wants
Icon(...) around it:
// before
new IconButton(Icons.Heart, onPressed: Like)
new EmptyState(Icons.Search, "Nothing here")
SelectedGlyph = Icons.HeartFilled
// after
IconButton(Icon(Icons.Heart), onPressed: Like)
EmptyState(Icon(Icons.Search), "Nothing here")
SelectedGlyph = Icon(Icons.HeartFilled)The errors are CS1503 and CS0029, and one site reported ten of them.
The conversion was removed rather than fixed because it could not be made to work: an implicit conversion on a framework type is silently DROPPED when the component crosses to JavaScript — the twin declared the glyph type and the call site emitted the raw string — so the same source built one thing on Photon and another on the web. A node is the same node on both.
A component that takes a capability through its constructor no longer takes it through its factory: the factory asks the container. So the arity drops, and the error reads as if your own generated surface lost an overload:
No overload for method 'ThemeToggle' takes 1 arguments
ThemeToggle(controller) → ThemeToggle()This is the point of the change — a site was threading an IThemeController through two
constructors to reach one button — but the first error you meet does not say so.
The compiler walks further than it used to, so a type that only ever runs on the server and happens
to live in the web project now fails with EQ2004 on things like CSharpCompilation.Create,
Stopwatch.StartNew or LoggerExtensions.LogInformation. Mark the TYPE:
[ServerOnly]
public sealed class PlaygroundCompilation { … }eQuantic.UI.Compiler pins Microsoft.CodeAnalysis.CSharp 5.9.0. A project that references
Microsoft.CodeAnalysis.CSharp.Features for its own tooling has to move to 5.9.0 in the same
commit, or restore fails with NU1107 — naming your test project rather than the SDK.
Earlier releases are not covered here: this page starts where it starts, rather than pretending to a history nobody recorded at the time.
🌐 English · Português
🏁 Start here
🏗️ Architecture
- Architecture Overview
- Write-Once Components
- Declarative Surface
- Package Architecture
- Components
- Styling
- Localization
- Analytics & GTM
📱 Write-once
⚙️ Compilation
⚡ Runtime
🔌 Server
🎨 Ecosystem
🚀 Development