eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsDesenvolvimento
Atualizar de versão
Editar esta página
58 min de leitura
O que cada release MUDOU debaixo de você, versão a versão — os erros de compilação que vai encontrar e a resposta de uma linha para cada um.
Cada entrada aqui foi encontrada por uma app real ao atualizar, não derivada de um changelog. Uma quebra que custou uma hora a alguém e nunca foi escrita custa a mesma hora à próxima pessoa.
Cada entrada abre com o que ficou mais simples, porque uma página que só lista quebras nunca diz se valeu a pena atualizar.
Para 0.2.0-preview.62
O que ficou mais simples. Uma biblioteca de classes compartilhada pelos projetos de uma solução é um ProjectReference e mais nada: o que uma página usa dela chega ao browser, uma classe estática, um record, um componente e as strings dele (Fluxo de build). Um campo num telefone abre o teclado da plataforma sem código seu, e a tela abre espaço para ele (Photon). O ICamera responde no Android como responde no iOS, pelo UseCamera(reason) e pelo CameraPreview que a app já escreveu (Capacidades). Uma app Android mantém a tela quando volta, gira sem cair, e o voltar fecha o que está aberto. Uma app iOS abre quando é compilada com o SDK do iOS 27, sem delegate e sem chave de plist sua. Uma app compila o que quer que as pastas, o nome do assembly e o título dela tenham, um $, uma crase ou uma aspa incluídos, e todo BunPackage que ela declara é instalado. E mais do C# que uma página roda no browser responde o que o .NET responde: uma data e um intervalo de tempo lidos e escritos na cultura da página, a caixa de um texto, o Split, as sobrecargas do LINQ, as codificações do Convert, e uma fila, uma pilha e um conjunto ordenado.
EQ2010: um componente chama o seu próprio membro [ServerOnly]
1
2
3
4
5
[ServerOnly]
private string Secret() => _vault.Read();
public override VisualNode Build(ComponentContext context) =>
Text(Secret()); // EQ2010
Um membro marcado [ServerOnly] é recusado no código do seu próprio tipo também. Compilava antes, e o módulo da página chamava um membro que o gêmeo não tem. Chame-o de uma [ServerAction], cujo corpo não atravessa.
Uma cultura que a chamada nomeia é a da página ou a invariante
1
2
3
4
5
DateTime.Parse(text, new CultureInfo("pt-BR")); // EQ2108
name.ToUpper(new CultureInfo("tr-TR")); // EQ2108
DateTime.Parse(text); // a cultura da página
DateTime.Parse(text, CultureInfo.InvariantCulture);
Uma cultura entregue ao Parse ou ao TryParse de uma data, de uma hora ou de um intervalo de tempo, ou ao ToUpper ou ao ToLower, atravessa como CultureInfo.CurrentCulture, uma nula ou CultureInfo.InvariantCulture. Cada uma compilava antes: o leitor falhava a página, e a mudança de caixa largava a cultura que recebia. A cultura da página é a que o servidor instalou, então uma página tr-TR põe i em maiúscula como İ sem nomear cultura nenhuma.
Uma data é lida na cultura da página
1
2
DateTime.Parse("01/02/2024"); // 1º de fevereiro numa página pt-BR
DateTime.Parse("01/02/2024", CultureInfo.InvariantCulture); // 2 de janeiro em toda página
Não é erro de compilação. O Parse e o TryParse dos cinco tipos de tempo liam as formas invariantes qualquer que fosse a página, então um valor escrito com ToString() não era lido de volta. Nomeie CultureInfo.InvariantCulture onde um texto está na forma invariante qualquer que seja a página: um valor de uma API, uma query string, um arquivo.
Um record de um projeto referenciado atravessa inteiro
Um record de outro projeto que uma Server Action responde tem um gêmeo agora, o record inteiro, como tem um record da própria app. Um método dele que um browser não consegue rodar para o build nesse método, mesmo que nenhuma página o chame. Deixe esse método fora do tipo que a página lê: um método de extensão numa classe própria é chamado do mesmo jeito (#826).
Mais formas que param o build
Formas que compilavam e depois respondiam errado no browser, ou falhavam lá, agora param o build:
•
EQ1004: um construtor de DateOnly que recebe um Calendar, como o de um DateTime já era, os estilos de um leitor que não sejam os de espaço em branco, um span entregue ao Parse ou ao TryParse, e um entregue ao Convert.ToBase64String ou ao Convert.ToHexString que não seja uma expressão de coleção.
•
EQ2004: ParseExact, TryParseExact e GetDateTimeFormats, que compilavam e falhavam a página.
Para quem hospeda o compilador ou escreve contra um shell
Uma app que escreve páginas não encontra nenhuma destas.
•
O ModuleBundler.Bundle recebe a pasta em que o bun roda, depois do caminho do bun.
•
O Eq.SplitOnWhiteSpace deixou de existir: um Split é Eq.StringSplit ou Eq.StringSplitAny.
•
O PhotonRealizer.Realize recebe composing depois de markedText, então uma chamada que passa pathCache ou um argumento posterior por posição passa a nomeá-lo.
•
Um ObjCBlock é construído sobre um ponto de entrada estático [UnmanagedCallersOnly] e um contexto, lido de volta com ObjCBlock.Context<T>(block), onde recebia um delegate.
•
No iOS a janela é do PhotonSceneDelegate, e o PhotonAppDelegate não a guarda mais.
O browser responde como o .NET
Não são erros de compilação. Uma página que dependia da resposta antiga recebe a do .NET agora:
•
TimeOnly.Parse("9:30 PM") é 21:30, onde eram nove e meia da manhã, e o TryParse(text, out value) dos cinco tipos de tempo responde true ou false.
•
Um TimeSpan escreve o formato que recebe (ToString(@"hh\:mm") é 01:02, onde todo formato escrevia 01:02:00) e é lido como o .NET lê um: 10 são dez dias, 24:00:00 vinte e quatro, e 00:60:00 lança.
•
O ToUpper e o ToLower mudam cada unidade de código para exatamente uma outra: ß continua ß, onde o browser escrevia SS.
•
O Split responde como a sobrecarga que o C# liga: um array de separadores divide em qualquer um deles, um separador vazio não divide nada, e a contagem e as opções valem. O LastIndexOf de uma string lê o início como o último char que uma ocorrência pode cobrir.
•
FirstOrDefault, LastOrDefault e SingleOrDefault com um valor padrão respondem esse valor, onde ele era chamado como predicado. Zip(second, third) responde triplas, o Average calcula no tipo do elemento, e o SequenceEqual com um comparador compara.
•
Um foreach sobre uma fila, uma pilha, uma lista ligada ou um conjunto ordenado que muda debaixo dele termina com a InvalidOperationException do .NET, onde foreach (var x in set) set.Add(x + 10); nunca terminava. O CopyTo deles e as operações de conjunto de um conjunto ordenado respondem, onde cada um era um TypeError, e uma origem nula entregue ao construtor de uma coleção lança a ArgumentNullException do .NET.
•
O Convert.ToBase64String e o ToHexString escrevem a codificação do .NET, onde cada um escrevia 1,2,255.
•
O alinhamento de um buraco de interpolação preenche: {name,Widths.Name} nomeava um membro que o gêmeo não tem.
•
No Android, o voltar fecha um diálogo, uma folha, uma gaveta ou um menu aberto antes de sair da app.
Para 0.2.0-preview.61
O que ficou mais simples. Um componente ouve o servidor: ele assina um tópico tipado, qualquer código no servidor publica no mesmo ServerTopic<T>, e o payload chega como o tipo C# que era, por uma conexão por página (Eventos do servidor). Uma página exige a sua autorização com o [Authorize] que uma Server Action recebe, e uma Server Action recusa um pedido que a página de outro site envia. Um layout segue a caixa onde cai: um Drawing preenche a coluna com SizeValue.Fill, um filho Positioned é posicionado por frações da pilha e deslocado por frações de si mesmo, e GridTrack.AutoFill(min) repete uma trilha quantas vezes couber. A tipografia segue a janela (TypeStyle.WithFluidSize), um controle mostra o seu pressionar e o seu foco em todos os alvos (BoxStyle.Pressed), e um alvo sob um ponteiro mantém um piso de 24dp. O editor de código completa enquanto uma palavra é digitada. Uma edição sob dotnet watch chega ao browser, e a página mantém o seu estado. Um número e uma data são impressos na cultura da página, a partir dos dados de formato que o próprio .NET guarda. E mais do C# que uma página roda no browser responde o que o .NET responde: uma classe, um record e uma struct são construídos e copiados como o C# os constrói e copia, uma classe aninhada e uma classe de exceção do app atravessam, e uma lista, um set, um indexador e um argumento nulo respondem como os do .NET.
Drawing.Width é um SizeValue
1
2
float w = drawing.Width; // não compila mais
float w = drawing.Width.Value; // a largura em dp; confira Width.Kind para um fill
A largura de um desenho agora pode ser um fill. drawing.Height é a altura que você deu, 0 quando o aspecto da arte a decide, e drawing.HeightAt(width) responde a altura numa largura. A construção não muda: new Drawing(art, 240) converte.
Um filho que preenche, ancorado de um lado, preenche até o outro, no Photon também
Um filho Positioned que preenche um eixo e está ancorado de um lado de uma pilha de tamanho conhecido agora preenche dessa borda até a oposta no Photon, como a web sempre desenhou (left: 100px; right: 0): numa pilha de 400dp, Start = 100 com uma caixa SizeValue.Fill dá uma caixa de 300 de largura em x 100, onde o Photon a punha com 400 e a deixava transbordar. Nada a mudar, a não ser que um layout nativo dependesse do transbordo.
O ServerActionHub deixou de existir
1
2
public sealed class Notifier(IHubContext<ServerActionHub> hub) // não compila mais
public sealed class Notifier(IServerEventPublisher events)
O hub, a sua rota /_equantic/hub e a chamada AddSignalR() que o AddUI fazia deixaram de existir. Aquilo para que se recorria ao hub agora é uma capacidade: declare um ServerTopic<T>, diga quem pode ouvi-lo em UseServerEvents, e publique com await events.PublishAsync(topic, payload) (Eventos do servidor). Um app que mapeia hubs SignalR próprios chama builder.Services.AddSignalR() ele mesmo.
Uma Server Action recusa um pedido de outra origem
Não é erro de compilação: é um 403 Forbidden onde a action rodava. Uma página servida de outro domínio que não o das suas actions lista essa origem, no Program.cs ou em EQuantic:ServerActions:AllowedOrigins no appsettings.json:
1
builder.Services.AddUI(options => options.AllowServerActionOrigins("https://admin.example.com"));
O host do próprio app é o Host do pedido, então um app atrás de um proxy que o reescreve chama app.UseForwardedHeaders() com os proxies em que confia, ou lista a sua origem pública. Um proxy que só termina o TLS não precisa de configuração, porque o esquema não é comparado. Um pedido sem Origin e sem Sec-Fetch-Site não é de um browser, e roda como antes (Segurança).
Um [Authorize] na classe protege a página também
1
2
3
[Page("/orders")]
[Authorize] // protegia as Server Actions da página, e agora protege a página também
public sealed class OrdersPage : StatefulComponent { … }
Um visitante anônimo é desafiado pelo esquema padrão do app onde a página era renderizada, e um autenticado sem a política recebe 403. Uma página que continua pública com as actions protegidas leva o atributo em cada action (Segurança).
EQ1009: dois construtores que recebem o mesmo número de argumentos
1
2
3
4
5
public sealed class Coded
{
public Coded(int code) { … }
public Coded(string text) { … } // EQ1009
}
Uma classe JavaScript tem um construtor, então um gêmeo tem um ramo por construtor C#, escolhido por quantos argumentos chegaram, e dois que recebem a mesma contagem não se distinguem. Até aqui o gêmeo guardava um construtor, e um new que queria outro rodava esse. Dê a um deles uma contagem de argumentos própria, ou diga o mesmo com uma factory estática: static Coded FromText(string text) => new(text.Length). Vale para toda classe, record e struct que chega ao browser. O construtor de cópia de um record não conta, nem o (SerializationInfo, StreamingContext) que o template de exceção do Visual Studio escreve.
Mais formas que param o build
Formas que compilavam e depois respondiam errado no browser, ou falhavam lá, agora param o build:
•
EQ2014: uma exceção construída ou testada onde um argumento de tipo que ela carrega é um parâmetro de tipo (new Failed<T>() num método genérico, catch (Failed<T>), uma classe de exceção declarada dentro de uma classe genérica). Construa e teste a exceção onde os seus argumentos de tipo são concretos.
•
EQ2010: um método de extensão de uma classe [ServerOnly] chamado num valor que atravessa (curve.Ease(t) é CurveEvaluator.Ease(curve, t)), e um tipo declarado dentro de uma classe [ServerOnly] ou de um atributo, seja como for que a página o nomeie.
•
EQ2007: um comparador que muda a igualdade entregue a ToDictionary, ToLookup, GroupBy ou Distinct. Tire-o, ou normalize as chaves na entrada. Um que pede o padrão passa.
•
EQ2004: um System.Range guardado numa variável ou entregue a um indexador que recebe o próprio Range, um method group sobre uma extensão do .NET (list.Any), e o GetEnumerator() de uma coleção do .NET devolvido pelo seu (devolva os itens com yield).
•
EQ1007: um segundo indexador num tipo, um membro em Item ou SetItem ao lado de um, e um tipo que implementa IEnumerable<T> sobre dois tipos.
•
Uma classe de exceção do app é transpilada como qualquer classe, então um catch tipado a encontra e os seus membros respondem no browser. Uma cujos membros alcançam o que o browser não roda falha o build com o diagnóstico desse membro: marque a classe com [ServerOnly].
E formas que falhavam o build agora passam: um dicionário construído com StringComparer.Ordinal (a regressão da .60), uma chamada, um elemento ou uma propriedade à frente de ?., um membro do .NET alcançado sem qualificação por using static onde a forma qualificada traduz, e um número escrito como texto sem cultura nomeada (o EQ2110 agora trata só de ler um, e o EQ2109 foi aposentado).
Um provedor de completion recebe um contexto e responde uma lista
1
2
3
4
5
Task<IReadOnlyList<CodeCompletionItem>> CompleteAsync(
CodeDocument document, CodePosition position, CancellationToken cancellation) // não compila mais
Task<CodeCompletionList> CompleteAsync(
CodeDocument document, CodePosition position, CodeCompletionContext context, CancellationToken cancellation)
Embrulhe os itens: new CodeCompletionList(items). O contexto diz o que perguntou (Trigger, e o Character que abriu a lista) e em qual Language, e uma lista marcada IsIncomplete é perguntada de novo sempre que a palavra muda. Uma linguagem derivada de CurlyBraceLanguage renomeia o seu override de Keywords para ReservedWords: Keywords agora é pública, as palavras reservadas, os tipos embutidos e as constantes numa lista só, que um completion oferece (Editor de código).
O otimizador de imagens recebe os formatos que codifica
1
2
options.UseImageOptimization(images => images.Formats = ["image/avif", "image/webp"]); // para o app na inicialização
options.UseImageOptimization(images => images.Formats = ["image/webp"]);
Formats recebe image/webp, image/png e image/jpeg, os formatos que um codificador daqui escreve. O AVIF nunca foi um deles: listado primeiro, era negociado com todo browser que o aceita e respondido com JPEG. O otimizador agora roda em SkiaSharp, com os nativos dentro do pacote: uma fonte .tiff é recusada, um GIF ou WebP animado é servido como está, e uma fonte acima de MaxSourceSize é recusada seja quem for que a entregue. new ImageOptimizer() e new BlurPlaceholderGenerator() recebem as ImageOptimizationOptions: resolva os dois pelos serviços, onde o UseImageOptimization os registra (Imagem).
Uma região de toque do Photon diz onde o controle é desenhado
1
2
var (bounds, node, path) = hit; // não compila mais
var (bounds, drawn, node, path) = hit;
Para um teste que lê o que o PhotonRealizer.Realize devolve. O HitRegion ganha Drawn, a caixa do próprio controle sem a folga que aumenta o seu alvo, depois de Bounds, no construtor também. TextRegion, SheetRegion e CodeRegion ganham membros no fim (Visible, e Offered numa região de código), então uma desconstrução posicional de uma os nomeia, e a construção compila como antes.
Um peso de flex é recusado onde é escrito
1
2
Spacer(flex: 0) // lança ArgumentOutOfRangeException: o peso de um spacer é 1 ou mais
Gap(12) // o espaço rígido
Não é erro de compilação. Um Flexible de peso zero não leva parte da sobra e mantém a sua base, ou o tamanho do seu conteúdo, onde o construtor elevava um zero a 1 e o item crescia. Um peso, uma base ou um shrink negativo lança onde é escrito, no servidor e no browser.
O que se desenha diferente
Não é erro de compilação, e não há nada a mudar, a não ser que um layout se apoiasse no desenho antigo:
•
Na web um Text é um bloco onde quer que esteja, com uma caixa de linha própria, e ocupa a largura que o pai dá. Como span inline numa pílula com padding, um rótulo 10/15 tinha 26,5px de altura onde as suas partes somam 21.
•
No Photon um Text ocupa a largura que o pai decide (um Box com largura, um Row ou um Column que o estica), então uma linha centralizada fica centralizada nessa largura, como na web. Um rótulo que deve abraçar o texto fica num Row, ou nomeia a sua largura.
•
BoxStyle.Focus é o foco do controle em que a caixa está, nos dois alvos. Na web não casava com nada, porque uma caixa nunca recebe foco, e o Photon nunca o desenhava.
•
O ScrolledStyle de um cabeçalho fixado aplica todos os membros em todos os alvos, um Elevation incluído.
•
No Photon um toque dentro da caixa do próprio controle é desse controle, antes da folga de qualquer vizinho, e uma caixa transformada recebe o ponteiro onde é desenhada.
•
Um Checkbox ou um Radio sem rótulo recebe um ponteiro em 24dp, onde o seu alvo era a própria caixa de 20dp.
O browser responde como o .NET
Não é erro de compilação. O código que se apoiava na resposta do JavaScript recebe a do .NET:
•
Um número e uma data são impressos na cultura em que a página foi renderizada, a partir do NumberFormatInfo e do DateTimeFormatInfo do próprio .NET, onde o Intl os montava pelas suas tabelas. Uma página sem cultura instalada formata na cultura invariante, nunca no locale do browser.
•
Um DateTime carrega o seu Kind: Now é local e a hora local é o fuso do browser, DateTimeOffset.Now carrega o offset do browser, um valor UTC atravessa o fio com o seu Z, e um componente fora do intervalo lança como o construtor do .NET.
•
Uma leitura null-conditional é null onde o seu valor é usado, nunca o undefined do JavaScript.
•
Um null entregue à sequência de um operador LINQ, a new Guid(text) ou ao Register de um token é pego por catch (ArgumentNullException e), e e.ParamName é o do .NET. CompareTo(null) responde 1.
•
Uma lista e um array ordenam pela ordenação do .NET, instável como a do .NET, BinarySearch responde o complemento do ponto de inserção numa falta, IndexOf, Contains e Remove comparam pela igualdade do tipo do elemento (um NaN encontra um NaN, um record encontra o seu igual), e List<int> copy = new(source) é uma cópia, onde era uma lista vazia.
•
Um HashSet<T> é a classe do runtime, pela igualdade do tipo do elemento, e um elemento acrescentado enquanto um foreach o percorre lança InvalidOperationException.
•
O IndexOf e o LastIndexOf de um char com um início e uma contagem buscam nesse intervalo, e um fora da string lança ArgumentOutOfRangeException.
•
strip[1..3] sobre um tipo do app chama Slice(1, 2), como o C#, onde chamava o slice(1, 3) do gêmeo.
•
A Message de uma exceção do framework é o texto que o .NET escreve, um construtor sem mensagem incluído.
•
Um record, uma struct e uma tupla comparam cada membro pelo comparador padrão do seu tipo, como o .NET: um array ou um List<int> guardado num deles compara por referência.
•
Um record genérico só é igual a um valor do seu próprio tipo fechado: new Box<int>(1) deixa de ser igual a new Box<double>(1).
•
with copia um record pelo seu próprio construtor de cópia quando ele declara um, e uma struct mutável ou uma tupla de valor é copiada onde o C# a copia, então uma escrita numa cópia deixa de aparecer na outra.
•
O gêmeo de uma classe roda o construtor que o new nomeia, com os seus inicializadores na ordem de declaração e os argumentos do seu : base(…), onde guardava o construtor mais largo e chamava o da base sem nenhum.
•
Um método declarado com new e chamado pelo tipo da base alcança o método da base.
•
Um dicionário chega ao browser enumerando na ordem em que o servidor o guardava (3, 1 onde chegava como 1, 3). No fio do SDK ele são os seus pares [chave, valor], então um cliente fora do SDK que chama uma Server Action à mão lê e envia um dicionário como [[chave, valor], …].
•
Uma Queue, uma Stack, uma LinkedList e um SortedSet atravessam como arrays na ordem em que cada um enumera, então um enviado a uma Server Action chega como a coleção.
•
Uma página responde HEAD como responde GET, onde um HEAD caía no fallback e respondia 404.
•
JavaScript escrito à mão que lê um valor transpilado encontra a sua forma C#: uma classe declarada dentro de outra é um módulo próprio chamado Owner$Inner, e um HashSet é o set do runtime.
Para 0.2.0-preview.60
O que ficou mais simples. Os dados de servidor de uma página chegam ao browser sozinhos: um campo, uma auto-property e um parâmetro capturado de um construtor primário hidratam, e um serviço que a página só lê atravessa como aquilo que ela lê, nunca inteiro. Um hover pode mover uma caixa e mudar a sua sombra, na web e no Photon. O título de uma rota chega à aba do browser, e duas páginas com o mesmo nome servem cada uma a sua rota. No Photon um leitor de tela ouve um radio, uma aba, um item de menu, uma opção e um destino de navegação como o que eles são, onde cada um era um botão. E mais do C# que uma página roda no browser responde o que o .NET responde: a ordem e o Add de um dicionário, o LINQ sobre qualquer sequência, os chars de uma string, o texto e os casts de um enum, um catch pelo seu tipo e pelo seu filtro, o GetHashCode, o texto de um Guid, uma data impressa pelo seu especificador, uma busca numa string pela sua comparação, e uma constante no seu próprio tipo. Um breakpoint dentro de uma lambda, de um inicializador ou de um corpo com parâmetro out para na sua própria linha.
O IServerRenderingService recebe o Type da página
1
2
await rendering.RenderPageAsync("HomePage", context); // deixa de compilar
await rendering.RenderPageAsync(typeof(HomePage), context);
O PreparePageAsync e o IsSsrEnabled mudam da mesma forma. O servidor guarda uma página pelo seu tipo, por isso duas páginas com o mesmo nome em dois namespaces respondem cada uma na sua rota.
O StyleDiff é uma classe
1
2
var background = style.Hover!.Value.Background; // deixa de compilar
var background = style.Hover!.Background;
BoxStyle.Hover, BoxStyle.Focus e Pinned.ScrolledStyle eram structs anuláveis e agora são referências anuláveis, então .HasValue vira is not null. Construir um com new StyleDiff { … } compila como antes.
EQ2114: um serviço que o código de browser da página usa de um jeito que o build não segue
Uma página cujo código de browser usa um serviço que o contêiner lhe entrega, de um jeito que o build não consegue seguir, falha o build com EQ2114, que nomeia a página, o valor e a expressão: um método chamado nele, o valor convertido em texto, comparado, enumerado ou guardado numa coleção, um membro que um tipo do .NET declara (o Count de uma lista), ou uma leitura enquanto a página é construída. Até aqui o serviço inteiro ia para o HTML da página. Decida no servidor, no PrefetchAsync ou num membro [ServerOnly], e guarde o resultado num campo, ou chame uma [ServerAction] quando o estado do browser é uma entrada.
A checagem de null num construtor é o caso comum:
1
2
_options = options ?? throw new ArgumentNullException(nameof(options)); // EQ2114
_options = options;
O browser constrói a página sem o valor, então a checagem lançava ali, e o contêiner já se recusa a construir uma página sem um serviço que ela exige.
Uma busca por uma comparação de cultura falha o build
1
2
name.Contains(term, StringComparison.CurrentCultureIgnoreCase) // EQ1004
name.Contains(term, StringComparison.OrdinalIgnoreCase)
O mesmo vale para StartsWith, EndsWith, IndexOf, LastIndexOf e Replace com uma comparação de cultura ou um CultureInfo. O browser não tem busca sensível à cultura, e a resposta que dava era a ordinal. Uma comparação guardada numa variável passa no build, e uma de cultura lança NotSupportedException quando a chamada roda. Equals e string.Compare por uma cultura comparam strings inteiras e continuam funcionando.
Mais duas formas que compilavam e quebravam no browser agora param o build: um literal UTF-8 ("ab"u8, EQ1004), e um membro do .NET alcançado sem qualificação por um using static que não tem tradução (EQ2004). Escreva o membro qualificado onde essa forma traduz, ou deixe-o no servidor.
Um dicionário construído com StringComparer.Ordinal falha o build
1
2
Dictionary<string, Job> jobs = new(StringComparer.Ordinal) { ["a"] = a }; // EQ1004 na .60
Dictionary<string, Job> jobs = new() { ["a"] = a };
Uma regressão desta release (#577), corrigida na próxima. Uma chave string compara de forma ordinal por padrão, no .NET e no browser, então tirar o comparador não muda nada. Um comparador que muda a igualdade, como StringComparer.OrdinalIgnoreCase, já era recusado e continua sendo, com EQ2007.
O browser responde como o .NET
Não é erro de compilação. O código que se apoiava na resposta do JavaScript recebe a do .NET:
•
O Dictionary.Add de uma chave que já existe lança ArgumentException, como no servidor. d[key] = value continua substituindo.
•
Um dicionário enumera na ordem do .NET, a de inserção enquanto nada é removido (d[3] = 30; d[1] = 10; lista 3, 1 onde listava 1, 3), e acrescentar uma chave enquanto um foreach o percorre lança InvalidOperationException.
•
O name de uma exceção é o seu tipo .NET, então o console e um componente contido imprimem InvalidOperationException: … onde imprimiam Error: ….
•
O foreach e o ToCharArray de uma string dão unidades de código UTF-16, então um emoji são dois chars.
•
o is int sobre um object com 2.5 é falso.
•
O CompareTo(string), e uma coleção ordenada de strings montada no browser, ordenam pela cultura atual (a, A, b, B), onde ordenavam por unidade de código.
•
IndexOf e LastIndexOf com um StringComparison checam um início e uma contagem como o .NET checa, e um que passa do fim lança ArgumentOutOfRangeException, onde o browser o limitava e respondia.
•
Um Guid lido de texto fica em minúsculas, como o .NET o escreve.
•
Um valor de enum que nenhum membro nomeia, enviado a uma Server Action, é recusado com um erro de JSON, onde era lido como o primeiro membro do enum.
•
O título de uma rota, [Page(Title = …)] ou MapPage<T>(route, title), dá título ao documento, onde o SetTitle do app sempre ganhava.
•
Um set ou um dicionário que o browser responderia de outro jeito fica fora da página: um com comparador próprio, um ordenado em ordem ordinal, ou um que guarda uma classe cuja igualdade nada consegue ler. Decida no servidor e guarde o resultado.
•
JavaScript escrito à mão que lê um valor transpilado encontra o seu tipo C#: um dicionário é a classe do runtime (get, keys(), for...of), uma constante long é um BigInt, e as chaves do payload de hidratação são os nomes que o gêmeo declara (downloads, não Downloads).
Para 0.2.0-preview.59
O que ficou mais simples. O editor de código compara dois textos: o CodeDiffer acha o que mudou, por linhas e por palavras, e a vista de diff mostra isso lado a lado ou em linha, sobre dois textos ou sobre um arquivo de um patch. Um atalho pode responder só enquanto o foco está dentro do seu componente, então dois do mesmo componente numa tela deixam de responder às mesmas teclas. E mais do C# que uma página roda no browser responde o que o .NET responde: um número impresso pelo seu especificador de formato, o Add* fracionário de uma data, um bool lido de texto, o List.Remove, o valor inicial de um membro de record, os membros de interface padrão de uma classe, e o escopo de uma variável que um padrão ou um out declara.
Uma desconstrução posicional de CodeGrid ou de ShortcutBinding pede três variáveis
1
2
3
var (origin, cell) = grid; // deixa de compilar
var (origin, cell, _) = grid; // o CodeGrid ganhou Rows
var (chord, onPressed, _) = binding; // o ShortcutBinding ganhou Scope
Cada um ganhou um membro opcional no fim, por isso construir com dois argumentos compila como antes.
A busca do editor de código responde dentro do editor
Não é erro de compilação. Cmd/Ctrl+F abre a busca de um CodeEditor só enquanto o teclado está nesse editor. Em qualquer outro ponto da página abre a busca do próprio browser, onde antes o último editor montado pegava as teclas onde quer que estivesse o foco.
A paleta escura ficou mais clara
Não é erro de compilação. O PhotonTheme, o tema de onde os templates partem, recalculou as cores escuras para que texto e controles passem no contraste APCA: o azul primário vai de #5CA2E8 para #76BAFF, o texto discreto de #8B95A3 para #ACB7C5, e uma borda forte de #3D4754 para #4C5664, com as cores de perigo, sucesso e informação levantadas da mesma forma. Um app que fixa screenshots do modo escuro vai vê-los mudar. Um app com um tema próprio, ou com o MaterialTheme, não é afetado.
Para 0.2.0-preview.58
O que ficou mais simples. O C# que uma página roda no browser responde o que o .NET responde: o texto de um número nos dois sentidos, uma divisão por zero, os operadores de um nullable, a precisão de um float, o default de um valor, uma chave ausente num dicionário. O editor de código ganha um motor próprio e recebe a entrada do jeito que cada plataforma a entrega, e um app publicado deixa de levar o C# dentro dos source maps.
Os tipos do editor de código foram para eQuantic.UI.Code
1
2
using eQuantic.UI.Primitives; // CodeDocument, CodeEditorController, CSharpLanguage… não estão mais aqui
using eQuantic.UI.Code; // acrescente este
Trinta e cinco tipos: o documento, o controlador, posições e intervalos, o realçador, o histórico e o keymap, as linguagens e as interfaces de provedor. A assembly chega junto com eQuantic.UI.Components, então o csproj não muda. CodeSurface recebe um ICodeSurfaceModel onde recebia um CodeEditorController, o que só afeta um host que monta a superfície ele mesmo.
ListDetail recebe builders
1
2
ListDetail(list: Inbox(), detail: Message()) // não compila mais
ListDetail(list: () => Inbox(), detail: () => Message())
Cada forma constrói a sua própria cópia dos painéis, então nenhum é montado duas vezes.
StyleClass e o resto de eQuantic.UI.Web.Styling não existem mais
StyleClass, Spacing, Breakpoint e [CompileTimeEvaluate], junto com StyleClass e StyleClasses em HtmlElement e IComponent. Um StyleClass acrescentava um nome de classe que nenhuma folha de estilo definia, então nada do que ele fazia se perde: estilize um elemento da saída de escape com HtmlStyle ou ClassBuilder.
Treze ícones adotaram o nome do upstream
era
é
LucideIcons.Trash2
LucideIcons.Trash
LucideIcons.Building2
LucideIcons.BuildingComplex
LucideIcons.History
LucideIcons.RotateCcwClock
TablerIcons.BrandKakoTalk
TablerIcons.BrandKakaoTalk
TablerIcons.CurrencyRubel
TablerIcons.CurrencyRuble
TablerIcons.Foodsteps
TablerIcons.Footsteps
TablerIcons.GenderTrasvesti
TablerIcons.GenderTravesti
TablerIcons.Ikosaedr
TablerIcons.Icosahedron
TablerIcons.MoodConfuzed, MoodConfuzedFilled
TablerIcons.MoodConfused, MoodConfusedFilled
TablerIcons.SportBillard
TablerIcons.SportBilliard
TablerIcons.BrandAdobeAfterEffect
TablerIcons.BrandAdobeAfterEffects
TablerIcons.Physotherapist
TablerIcons.Physiotherapist
Cada um é o mesmo desenho. O próprio LucideIcons.Trash agora desenha o que o Trash2 desenhava, o cesto com duas linhas, e nenhum erro de compilação avisa.
Um app publicado não escreve source maps
Não é erro de compilação. Um build Release não escreve nenhum e um publish nunca leva um. Um build Debug os escreve com o C#, para o depurador do browser. Um relator de erros que envia os mapas ele mesmo define <EQuanticSourceMaps>external</EQuanticSourceMaps>, que os escreve sem o C#. Até aqui todo app publicado levava o C# dos seus componentes nos mapas, corpos de [ServerAction] incluídos.
equantic.css não é mais escrito
Um app que ligava /_equantic/equantic.css pelo ConfigureHtmlShell remove o link: a folha gerada já vai inline.
O browser responde como o .NET, exceções incluídas
Não é erro de compilação, e é o que mais provavelmente aparece numa página rodando: código que se apoiava na resposta do JavaScript recebe a do .NET.
•
int.Parse("12abc") lança FormatException onde respondia 12, e um TryParse que falha deixa 0, não NaN.
•
Uma divisão inteira ou um resto por zero lança DivideByZeroException onde a página mostrava Infinity ou NaN.
•
x += 1 num int? nulo continua nulo onde virava 1.
•
Uma chave ausente num SortedDictionary, num SortedList ou num Dictionary com chave de valor lança KeyNotFoundException, e um TryGetValue que não acha escreve default.
•
true.ToString() é True.
•
int x = default é 0 e new long[3] guarda longs, onde eram undefined e números comuns, e um filtro escrito o.Status != default(OrderStatus) compara com o zero do enum, onde todo item passava.
Para 0.2.0-preview.57
O que ficou mais simples. Um componente pode ser escrito sobre uma base sua, e um componente que a página compõe pode carregar os próprios dados de servidor. As duas coisas eram prometidas e nenhuma valia: a subclasse de uma base do app só levava o Build e morria no browser, e um IServerPrefetch abaixo da página nunca era chamado. O runtime também passa a ter uma cópia committed de cada módulo transpilado, em vez de duas.
As declarações nativas foram para eQuantic.UI.Native.Hosting
1
2
using eQuantic.UI.Primitives; // PhotonEntitlements, AppCategory… não estão mais aqui
using eQuantic.UI.Native.Hosting; // acrescente este
PhotonCapabilityAttribute, PhotonEntitlementAttribute, PhotonEntitlements, PhotonBundleKeyAttribute, PhotonBundleValueKind e AppCategory. Um app Photon já referencia a assembly de hosting — é de lá que vem o CreateApp —, então é um using só. Um app web nunca conseguiria usá-los de verdade e não é afetado.
RangeValue carrega só números
1
2
Progress(child, value: new RangeValue(0.45f, 0, 1) { Text = "Estimando" }) // não compila mais
Progress(child, value: new RangeValue(0.45f, 0, 1), valueText: "Estimando")
As palavras foram para o nó — Progress.ValueText e Adjustable.ValueText —, porque uma barra indeterminada não tem número e perdia as palavras junto com ele. Vindo da .55, é a segunda mudança no tipo: a .56 renomeou AdjustableValue para RangeValue, e a .57 tira o Text dele.
SemanticNode se desconstrói em treze partes
1
2
3
4
var (role, path, bounds, label, value, disabled, isChecked, expanded,
current, selected, heading, live) = node; // não compila mais
var (role, path, bounds, label, value, disabled, isChecked, expanded,
current, selected, heading, live, range) = node;
Construir não muda: Range é um parâmetro com valor padrão.
ImageOptimizationState não existe mais
Ele era escrito pelo UseImageOptimization e lido por nada. Leia ImageOptimizationOptions do DI em vez disso, que é o que o endpoint /_equantic/image faz a cada requisição.
Quatro overloads viraram um cada, e a maioria das chamadas continua compilando
•
UI.Glyph(glyph) vira UI.Icon(glyph). UI.Icon e new Icon recebem um IconGlyph, com uma conversão implícita a partir de Icons, então UI.Icon(Icons.Check) fica igual.
•
EmailRenderer.Render(component, theme) liga na forma de VisualNode, que um componente já é, com os mesmos parâmetros.
•
WebRealizer.Lower(node, theme, scale, styles) é um método só, com StyleSink? styles = null.
•
CodeWriter.BeginScope(line, opener, closer) saiu; a forma de abertura e fechamento fica.
EQ2012: um [ServerAction] num componente abstrato
1
2
3
4
public abstract class CardBase : StatelessComponent
{
[ServerAction] public Task<string> Load() => …; // EQ2012
}
O servidor registra uma ação sob o nome do componente CONCRETO, então um stub na base invocava um id que nada servia. Declare a ação no componente concreto.
Um IServerPrefetch composto agora roda
Não é erro de compilação, mas muda o que acontece: um componente que implementa IServerPrefetch abaixo da página era pulado, e agora o PrefetchAsync dele roda e os campos chegam ao payload de hidratação. Um componente que implementava esperando que nada acontecesse vai descobrir que algo acontece.
Para 0.2.0-preview.56
O que ficou mais simples. Nenhum membro sobrevive para manter viva uma forma antiga. O TypeStyle e o SemanticNode carregavam um construtor e um Deconstruct acrescentados para que um assembly compilado contra um pacote mais velho ainda ligasse, e os dois saíram junto com o parágrafo que os explicava. Uma extensão sobre o vocabulário agora é transpilada como qualquer outra, então node.Centered() vira VisualNodeExtensions.centered(node), e os dois espelhos mais a costura que aquela exceção exigia foram junto. Isso também fecha um buraco que nenhum autor em C# conseguia ver: um parâmetro de componente com o nome de um membro do runtime sobrescrevia esse membro e derrubava a página no browser com centered is not a function.
O AdjustableValue virou RangeValue
1
2
Value = new AdjustableValue(now, min, max) // não compila mais
Value = new RangeValue(now, min, max)
Os mesmos três números e o mesmo Text opcional. O nome mudou porque um segundo nó passou a carregar o trio: o Progress.Value também é um RangeValue, e uma barra de progresso não é ajustável.
O WebFrame recebe o conteúdo como um valor só
1
2
3
4
new WebFrame { Source = "https://example.com", Title = "Docs" } // não compila mais
new WebFrame { Document = "<p>hello</p>" } // não compila mais
WebFrame(WebContent.Url("https://example.com"), "Docs")
WebFrame(WebContent.Document("<p>hello</p>"), "Note")
Um frame mostra uma URL ou marcação embutida, nunca as duas, e duas propriedades init não conseguiam dizer isso. O título é argumento do construtor pela razão de ele existir: um frame que ninguém consegue nomear é um que o leitor de tela não consegue anunciar.
O Navigable recebe o manipulador primeiro
1
2
new Navigable(rows, onMove) // não compila mais
new Navigable(onMove, rows)
Os filhos vão por último, como em todo contêiner do vocabulário.
As factories de gráficos espelham os seus records
1
2
ChartSeries(name: "2026", values: [15, 21]) // não compila mais
ChartSeries(Name: "2026", Values: [15, 21])
Os parâmetros de um record posicional SÃO as propriedades dele, então new ChartSeries(Name: …) e a factory passam a ter uma grafia só entre as duas. Só quebra quem usa argumento NOMEADO, e o CategoryAxis e o ValueAxis mudaram do mesmo jeito.
O TypeStyle e o SemanticNode se desmontam na forma real
1
2
var (size, line, weight, tracking, maxScale, mono, italic) = style; // não compila mais
var (size, line, weight, tracking, maxScale, mono, italic, family) = style;
Os dois carregavam um Deconstruct mais curto (e o TypeStyle um construtor a condizer) para que código compilado contra um pacote mais velho ainda ligasse. Construir não muda, porque os parâmetros novos têm valor padrão. Só a desmontagem muda, e o compilador diz a aridade que ele quer.
O LinkRegion e o RealizeResult carregam mais
1
2
var (bounds, destination) = region; // não compila mais
var (bounds, destination, path) = region;
Uma região de link agora sabe o caminho de layout de onde veio, e é isso que permite chegar nela pelo Tab e ativá-la no Photon. Só afeta um host que leia as regiões realizadas, e o construtor do RealizeResult cresceu pela mesma passada.
Para 0.2.0-preview.55
O que ficou mais simples. Todo despacho sobre o vocabulário agora é um visitor: o motor de layout, a passada de pintura do Photon, o realizador web, o DOM do servidor, a passada de semântica e as duas alternativas do email. Um nó que um realizador esqueceu vira erro de compilação em vez de cair num braço default, e foi assim que Navigable e WebFrame apareceram, medidos em silêncio como uma caixa de tamanho zero. Os vinte wrappers que envolvem um único filho compartilham uma base, SingleChildNode, onde antes quatro listas mantidas à mão discordavam em oito lugares. Isso aparece numa tela nativa: um Text dentro de um Pressable, um Link ou um Hoverable, numa Row estreita demais para ele, agora trunca como um Text solto em vez de passar 128dp da borda da linha. E um slider diz o valor que tem, na web e para o VoiceOver e o TalkBack.
ButtonStyles saiu
Era só uma visão sobre o Sizing, e a escada agora é a única declaração das métricas de um controle. Cada campo de Metrics(size, density) já era produzido pelo degrau que ele agora nomeia, então nenhum número muda:
1
2
3
4
5
6
7
8
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 é só do host
1
FlexNode container = row; // EQ2010 num componente
O runtime nunca exportou um gêmeo do FlexNode, então um componente cliente que o nomeava compilava e depois morria na hidratação com "does not provide an export named". Agora o eqc avisa no ponto da chamada. Nomeie o nó concreto, Row ou Column. Row(...).With(child) continua compilando, porque a cerca pergunta por onde o membro foi alcançado. O código do servidor e a track nativa não mudam.
EQ2111 quando uma busca de serviço não tem por onde chavear
Uma chamada GetService sem argumento de tipo e sem argumento emitia getService(), que o runtime não consegue responder, porque o registro dele é indexado pelo NOME da interface. Nada avisava. Agora é EQ2111, o mesmo erro que o outro caminho até essa situação já dava. Se o seu build parar aqui, essa chamada já não devolvia nada no browser na .54: passe o tipo.
Um Adjustable sem valor se anuncia como group
O ARIA exige que role="slider" traga aria-valuenow, e todo slider que o SDK entregou trazia o papel sem o valor. Agora o papel é derivado do valor. Um Adjustable no papel padrão é um slider quando tem um Value e um group quando não tem, que é o que um contêiner focável ajustado pelas setas é. Um valor em Tablist ou Radiogroup, papéis que não carregam valor, não é emitido.
1
2
Adjustable(track, onAdjust, value: new AdjustableValue(now, 0, 1000) { Text = "R$ 400" });
Slider(budget, onBudget, valueText: "R$ 400");
O Slider do SDK já entrega o valor a partir do qual o thumb é desenhado, e valueText troca o número por palavras, porque só o app sabe o que os números dele significam.
Para 0.2.0-preview.54
O que ficou mais simples. A geometria tem uma casa só. Rect, Point e Size eram declarados no motor nativo e copiados quatro vezes para chegar aos sítios que precisavam deles; agora vivem em eQuantic.UI.Primitives, ao lado do resto do vocabulário, portanto um componente, um gráfico e um realizador nomeiam o mesmo tipo. Um pintor recebe essa caixa em vez de quatro floats soltos. E a hierarquia de nós é fechada, o que significa que um realizador a quem falta um caso é um erro de compilação em vez de um nó que silenciosamente não desenha.
Primeiro uma correção à .53, porque saiu errada. A entrada da .53 dizia que um rótulo nativo truncado "agora acaba em …". A marca estava certa e o SÍTIO não: passámos ao CoreText a constante 2 a acreditar que significava truncar-no-fim, e 2 é kCTLineTruncationMiddle. Ou seja, no macOS a .53 cortava os rótulos ao MEIO — "Documentos e Definições" ficava "Docum…nições". A .54 põe as reticências no fim, onde a entrada sempre afirmou que estavam. Encontrado por um consumidor, não por nós.
A geometria mudou para Primitives
Rect, Point, Size, RRect e Matrix2D mudaram de eQuantic.UI.Native.Engine para eQuantic.UI.Primitives. Recompilar a partir do source é a migração toda — os dois assemblies saem no mesmo SDK e na mesma versão — e só um consumidor que segure uma referência compilada é que religa. Sem type forwarders: em preview uma quebra é de graça, e um forwarder é um segundo nome para o tipo de que acabámos de remover quatro cópias.
A mesma mudança, pela mesma razão, para SemanticRole, SemanticCheck e SemanticNode, que saem de eQuantic.UI.Native.Components.
Os operadores do Point são só-do-host, e o RRect e o Matrix2D também
1
var meio = a + b; // EQ2010 num componente
O JavaScript não sobrecarrega operadores, portanto a + b em dois Point emitia o + do próprio JavaScript e concatenava dois objetos em "[object Object][object Object]"; a * 2 dava NaN. Fazia isto em silêncio, que é a razão de agora ser um erro de compilação em vez de uma página a mostrar o número errado. No servidor e na track nativa funcionam como antes. Num componente, escreva a aritmética:
1
var meio = new Point(a.X + b.X, a.Y + b.Y);
RRect e Matrix2D estão cercados inteiros — são o que um rasterizador consome, e uma página que nomeasse Matrix2D estava a emitir um import para um símbolo que o runtime não exporta.
O ICanvasPainter recebe uma caixa e um ponto
1
2
3
4
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; }
Só quem escreveu um pintor PRÓPRIO é que muda alguma coisa. Código que desenha no pintor que nós entregamos fica igual. Os quatro floats eram sempre os quatro que um Rect é, soletrados em cada chamada.
A superfície de rotas do runtime
routeData() e o tipo RouteData desapareceram. RouteValues.from(params, query) e RouteValues substituem-nos, e o getCurrentRoute() continua a existir e responde um RouteValues — um rename e um import. Dois comportamentos mudaram com isso:
•
param e query respondem null onde o TypeScript respondia undefined, porque o Param do C# devolve string?. O ?? não é afetado; uma comparação === undefined é.
•
Uma chave de query repetida responde o PRIMEIRO valor dos dois lados. ?tag=a&tag=b chegava a uma página como "a,b" vindo do servidor e "a" no browser; agora ambos dizem "a".
A superfície de serviços do RenderContext é um método só
Foram-se cinco membros, não um: ServiceProvider, SetGlobalServiceProvider, SetScopedServiceProvider, RegisterService<T> e TryGetService<T>. Fica o GetService<T>(), e devolve T? onde devolvia T.
1
2
3
context.RegisterService(new Clock()); // desapareceu — registe no container da própria app
var clock = context.TryGetService<IClock>(); // desapareceu
var clock = context.GetService<IClock>(); // agora T?, portanto a verificação de nulo é sua
O TryGetService nunca teve gémeo nenhum — o transpilador emitia context.tryGetService(...), um método que o runtime nunca teve, portanto qualquer componente que o chamasse já estava partido no browser.
O parâmetro do Navigator.Go é destination
1
2
3
Navigator.Go(href: "/relatorios"); // deixa de compilar
Navigator.Go("/relatorios"); // continua bem
Navigator.Go(destination: "/relatorios");
Uma chamada posicional fica intacta. Só quebra com argumento NOMEADO, que é o tipo de quebra que o compilador reporta com clareza e que ninguém espera encontrar nas notas de versão.
O BarRect leva um Rect
1
public sealed record BarRect(int Category, int Series, Rect Box, bool Negative, bool DataEnd);
Eram oito parâmetros posicionais com X, Y, Width e Height soletrados. O construtor gerado, o Deconstruct e essas quatro propriedades foram com a mudança: leia bar.Box.X em vez de bar.X, e construa com um Rect. Só afeta código que lê diretamente a geometria resolvida do gráfico — o BarChart em si fica intacto.
Para 0.2.0-preview.53
O que ficou mais simples. Uma face de código de marca funciona para o tema que a tem — o IAppTheme.MonoFamily saiu na .51 e era inerte precisamente para os temas para que foi feito, por isso se nomeava a face em cada chamada para contornar isso, pode parar. Um link de marcador aterra no mesmo sítio a frio e a quente. Uma folha de cálculo existe no HTML do servidor. E o motor de layout nativo deixou de ter estado mutável, o que não muda nada do que você escreve e muda bastante do que quem o lê tem de segurar na cabeça.
Nada quebra. Nenhuma API se mexeu, nada foi renomeado, e a única mudança de código é dentro do track de layout nativo, cujo único chamador é o próprio SDK.
A face de um papel é um valor por omissão, não uma escolha
O MonoFamily não fazia nada para a forma normal de marcar tipografia:
1
public TypeStyle Type(TypeRole role) => Base.Type(role) with { Family = "IBM Plex Sans" };
Todos os papéis nomeiam uma face, portanto a face de código do tema encontrava sempre a Family já preenchida e afastava-se, pela regra de que um estilo que NOMEIA uma família fica com ela. Só que nada naquela família tinha sido escolhido para aquele texto — veio do PAPEL. Logo a feature funcionava para um tema que deixa a Family nula e era inerte para um que não deixa, que é ao contrário.
O que muda para si: se nomeou a face de código em cada chamada para contornar isto, essas linhas ficam redundantes, não erradas.
Um carregamento a frio com fragmento aterra onde um quente aterra
A .52 corrigiu isto e a correção não funcionou. A medição que o explica vale a pena guardar:
1
2
frio, loadEventEnd 1310 ms → certo
quente, loadEventEnd 36 ms → errado
Quanto mais rápida a página, mais fiável a falha. A correção da .52 gastava a única oportunidade no load, e o load pode disparar ANTES de o browser fazer o salto do fragmento. Um visitante que volta é quase toda a gente, e estava sempre na coluna partida.
Agora vigia frames, com limite de orçamento depois de o documento completar e um relógio de parede faça a página o que fizer — e reforma-se quando o leitor navega, para não o seguir para outra página.
Nada a mudar no seu código. Um empurrão de scroll que tenha acrescentado para contornar isto fica redundante.
Uma folha de cálculo existe no HTML do servidor
O SheetSurface não tinha braço no realizador web, portanto o SSR escrevia um <span> vazio onde o browser desenha uma grelha. Desde o dia em que saiu: um crawler nunca via os dados, um leitor sem scripts via um buraco. O servidor escreve agora a grelha, o filho, o role="grid", o nome e um tabindex.
O CodeSurface fica deliberadamente de fora por agora — o cliente acrescenta um caret a cada editor, portanto uma árvore de servidor só com o filho fica com um elemento a menos do que a hidratação espera, e essa forma é uma decisão ainda por tomar.
Uma linha cortada acaba de uma só maneira
O ITextMeasurer promete reticências desde que foi escrito, e uma linha cortada acabava de quatro maneiras diferentes. O CoreText passa a truncar no próprio layout, portanto a largura reportada inclui a marca e os glifos desenhados são essa mesma linha.
Como encontrar: uma etiqueta truncada no macOS acaba em … e mede um pouco mais que antes, porque uma truncagem vai até ao caráter onde uma quebra pára na última palavra inteira. Se guarda goldens de píxeis próprios sobre texto nativo truncado, eles mexem-se uma vez. O DirectWrite continua a cortar sem marca.
CORREÇÃO, E É A0.2.0-preview.53 que está errada. Essa versão corta o MEIO de uma etiqueta truncada no macOS — Replace hardco…with IAppTheme — e não o fim. Passámos a constante de truncagem do CoreText 2 com um comentário a chamar-lhe kCTLineTruncationEnd, e o CTLine.h diz que 2 é kCTLineTruncationMiddle. Portanto a marca lá está, no sítio errado, e o realizador web ao lado corta no fim como o design system pede. Corrigido depois da .53, com a afirmação que faltava: duas strings com a mesma cabeça têm de cortar à mesma largura. Só o texto nativo é afetado — a web sempre esteve certa.
LayoutConstraints, se hospeda o motor de layout
Só layout nativo. O LayoutContext deixou de carregar IndeterminateWidth, IndeterminateHeight, StretchWidth e StretchHeight — são factos sobre o que um pai ofereceu a um filho, portanto viajam na constraint do filho. O LayoutEngine.Layout ganhou um rootStretch opcional.
Neutro em comportamento e medido assim: os goldens de píxeis intactos e a alocação oito bytes por frame MAIS BAIXA nos dois caminhos, porque o objeto de contexto tem menos quatro campos.
Para 0.2.0-preview.52
O que ficou mais simples. Uma coisa que pode deixar de contornar: um link de marcador aterra no mesmo sítio quer o leitor o tenha seguido dentro da sua app, quer o tenha colado num separador novo. Se tinha um empurrãozinho de scroll próprio à frente disso, pode sair.
Nada quebra. Nenhuma API se mexeu, nada foi renomeado, e não há aqui comportamento nenhum que tenha de ir procurar — a única mudança é uma correcção que teria notado como leitor.
Um carregamento a frio com fragmento aterra onde um quente aterra
Abrir um link como /privacy#rights num separador novo deixava o alvo DEBAIXO do cabeçalho fixo, enquanto navegar para o mesmo fragmento de dentro da app o punha no sítio certo. Esse par é o diagnóstico inteiro: o scroll-margin-top a ler a altura medida da barra estava certo, e só a ORDEM estava errada.
A barra é medida depois de um passe de render, portanto num carregamento a frio o browser já saltou com a altura ainda por saber. Existia uma correcção exatamente para isso, com uma oportunidade só, que era gasta na MEDIÇÃO e não na correcção — e como o browser volta a correr o salto à medida que conteúdo tardio assenta o layout, essa medição aterrava muitas vezes com o alvo ainda lá em baixo. Nada a corrigir, oportunidade perdida.
Agora gasta a oportunidade na correcção, e marca uma segunda no próprio load do documento, medindo a barra aí em vez de confiar no número que marcou a segunda volta — uma barra que quebra de linha, ou que cresce com uma webfont tardia, é mais alta no load do que na primeira pintura.
Como encontrar: abra um link de marcador para uma página longa num separador novo, com cabeçalho fixo nessa página. O título da secção ficava atrás do cabeçalho; agora fica por baixo dele.
Nada a mudar no seu código. Se acrescentou um empurrão de scroll para contornar isto, fica redundante, não errado.
Para 0.2.0-preview.51
O que ficou mais simples. Um tipo de letra de marca é uma propriedade no seu tema (TypeStyle.Family por papel, IAppTheme.MonoFamily para código) em vez de um StyleOverride em cada chamada, e uma face que a máquina não tem é NOMEADA na linha da execução em vez de substituída em silêncio. O Text.Align funciona no Photon, logo texto centrado deixa de precisar de uma caixa à volta. Um nó disposto conhece o seu Parent, logo "onde estou" deixa de ser uma string de caminho a ser interpretada. E dois mistérios passam a erros de build com remédio: um que só aparecia na hidratação (EQ2010) e um que nomeava um ficheiro gerado que você nunca tinha visto (EQ3006, uma app a adotar o SDK que manteve o seu próprio Main).
Nada foi renomeado. Um membro mudou de forma. Cinco comportamentos mudaram debaixo de código que continua a compilar, e o compilador não ajuda em nenhum deles — por isso cada um diz como o encontrar.
LayoutNode.Children é só de leitura — a única quebra de código
1
public IReadOnlyList<LayoutNode> Children { get; } // era List<LayoutNode>
node.Children.Add(child) deixa de compilar. Todo nó disposto carrega agora Parent, recursivamente até à raiz, e anexar é trabalho do motor para que a ligação não possa ser esquecida. Ler não mudou, e foreach (var child in node) — iterar o NÓ, não a lista — não aloca nada nos caminhos por frame.
EQ2010 — um símbolo de host nomeado num componente
Um componente que chamasse FaceName.Usable(...) ou lesse FaceResolution.Unresolved compilava, emitia, e morria na hidratação com "does not provide an export named" enquanto o SSR continuava a responder 200. Agora é erro de build. Mova a chamada para um [ServerAction], uma classe [ServerOnly] ou um realizer. A cerca é por SÍMBOLO, portanto FaceName.IsWellFormed continua a atravessar e Usable não.
Um Mac em modo escuro abre a app em modo escuro
PhotonOptions.Mode = null documenta "segue o sistema" desde que existe, e o shell do macOS respondia Light incondicionalmente — o único alvo de quatro que não honrava isso.
Como encontrar: abra a sua app num Mac com modo escuro ligado. Se dependia do comportamento antigo, diga-o com Mode = ThemeMode.Light em vez de depender de um defeito.
O Photon honra o Text.Align
Os três serviços de texto cortavam o raster justo à linha mais longa e desenhavam cada linha em x=0, portanto o Text.Align não tinha para onde ir e era descartado.
Como encontrar: qualquer Text que definia Align e parecia alinhado à esquerda no Photon move-se agora. Um Row ou uma caixa posicionada à mão que tenha acrescentado para contornar isto passa a centrar duas vezes.
Uma cultura regional cai pelo PAI
O catálogo plano de strings do cliente era dobrado apenas a partir da cultura NEUTRA, logo pt-BR saltava o pt: uma chave traduzida uma vez em Strings.pt.resx voltava em INGLÊS no cliente enquanto o servidor a renderizava em português.
Como encontrar: uma cultura regional cujo pai carrega traduções mostra mais delas agora. As chaves que copiou para o .resx regional para contornar isto ficam redundantes, não erradas.
Variant.Link carrega a tinta em Base e Pressed
O tema tinha ambos transparentes com a tinta em OnBase, e a tinta de pressionado não existia de todo — portanto a troca de texto pressionado que o Button delega não tinha para onde trocar. Um tema ou realizer próprio que leia Colors(Variant.Link).Base recebe agora uma cor. Nada PREENCHE um Link, logo a tinta fica ali sem pintar retângulo nenhum — o que um Link nunca tem é preenchimento, e esse é o slot Subtle.
Estilo web: o papel estiliza a classe, o nó estiliza o elemento
A face, a inclinação, a pilha monoespaçada e o white-space de um papel de código saíram dos estilos inline para a classe .eq-type-*. O lowering do cliente não consegue ler a escala tipográfica do tema, portanto uma propriedade de papel emitida inline pelo SSR era descartada no primeiro re-render — uma discrepância de hidratação, não um detalhe cosmético.
Como encontrar: CSS escrito à mão que sobrepunha um font-family, font-style ou white-space inline no nosso texto compete agora com uma classe em vez de um estilo inline, portanto ganha onde antes perdia. Os controlos de entrada recebem a face por .eq-entry.eq-type-* em vez de um font-family: inherit inline.
Para 0.2.0-preview.50
Nada foi renomeado e nada foi removido. O que esta release mudou foram quatro comportamentos por baixo de código que continua a compilar, e um pacote que passou a ser outra coisa. O compilador só o ajuda no último, portanto cada um dos outros diz como o encontrar.
O IWorkspace.OpenUrl só entrega os esquemas que a sua app abre
Entregar um URL ao sistema operativo é entregá-lo a quem quer que reclame o esquema: https chega a um navegador, file: lança o que o caminho nomeia, e qualquer esquema que outra app tenha registado corre essa app. Um link que chegou em conteúdo não pode escolher entre esses, por isso o OpenUrl consulta agora uma política primeiro. Ela entrega http, https e todo o esquema que você já declarou com builder.Bundle.UrlScheme(…). Tudo o resto é uma linha no Program.cs:
1
2
3
builder.Workspace.OpensMail(); // mailto:
builder.Workspace.OpensPhone().OpensMessages(); // tel:, sms:
builder.Workspace.Opens("x-apple.systempreferences"); // o esquema de outra app, ou do sistema
Como o encontra: a chamada devolve false e a realização regista um aviso com o esquema e a linha exata que o abriria, portanto o primeiro clique diz-lhe. Não lança exceção, porque a política existe para URLs que a sua app não escreveu. O CanOpen aplica a mesma política, por isso um botão condicionado por ele desaparece exatamente para os links que o OpenUrl recusa.
file: não se abre por opt-in. Use o OpenFile ou o Reveal, que recebem um caminho, verificam que existe e nunca lançam uma pasta. E o builder.Bundle.UrlScheme("acme://") passa a lançar na própria linha, em vez de escrever uma entrada de manifesto que nada casa.
O VisualNode.Key passa a fazer o que sempre disse que fazia
O Key está documentado como identidade do reconciliador desde que o vocabulário existe, e nenhum dos dois realizers o lia. Agora ambos leem, o que é uma correção: uma lista virtualizada que desliza a janela deixa de renumerar os filhos, portanto o anel de foco deixa de sair da linha onde estava.
Como o encontra: torna real um contrato que ninguém verificava. Chaves duplicadas entre irmãos eram inofensivas porque ninguém as lia. Se você constrói a chave a partir dos seus próprios dados, sendo o row.Key de um DataTable a forma a procurar, confirme que é única dentro da lista.
O scroll do foco salta em vez de deslizar
Mover o foco encaixa o alvo à vista. Um deslize não tinha chegado no frame que desenha o anel de foco, portanto um percurso de teclado podia ler o anel como fora do ecrã. A roda e o arrasto continuam a deslizar.
Duas correções de layout que podem mexer em algo
Um stack passa a medir os filhos contra a sua própria caixa, onde antes lhes oferecia as restrições de entrada, portanto um filho Fill num stack de altura fixa media zero dentro de um scroll. Se você fixou uma altura para contornar isso, o remendo pode sair. E um canvas com handlers fica com o ponteiro, onde as camadas de um Stack desligavam o pointer-events e todo o hover caía por baixo.
O eQuantic.UI.Charts é outro pacote
O único erro de compilação aqui, e o que ninguém deve encontrar: nenhuma das três apps que seguem este SDK referenciava esse pacote. Era um pequeno assembly partilhado pelos dois wrappers só-web; passou a ser a biblioteca de gráficos write-once, e os três tipos mudaram-se para os wrappers que os usam.
Se você usava
Agora é
eQuantic.UI.Charts.IChart com o wrapper do Chart.js
eQuantic.UI.Charts.ChartJs.IChart — troque o using
eQuantic.UI.Charts.IChart com o wrapper do ApexCharts
eQuantic.UI.Charts.ApexCharts.IChart — troque o using
ChartData<T>, Dataset
Mesmo nome, mesmo namespace; passam a vir dentro do eQuantic.UI.Charts.ChartJs, que você já referencia
Se você usa os wrappers pelos pacotes deles, não tem nada a fazer. Só um projeto que referenciava o eQuantic.UI.Charts diretamente por causa desses tipos tem de se mudar.
Para 0.2.0-preview.49
O eQuantic.UI.Core deixou de existir. Nunca foi um assembly: eram quatro coisas sem relação a partilhar um nome, e toda app web instalava as quatro para usar algumas. Cada parte mudou-se para o seu único consumidor.
O quê
Onde vive agora
[Page], [ServerAction], [ServerOnly], [Authorize], [AllowAnonymous]
eQuantic.UI.Primitives, ao lado dos outros atributos de contrato
HtmlElement, HtmlNode, HtmlStyle, DynamicElement, IComponent, RenderContext, ClassBuilder
eQuantic.UI.Web, onde o DOM é realizado
SeoBuilder, MetadataCollection, AssetCollection, IRequireAssets
eQuantic.UI.Server.Metadata / eQuantic.UI.Server.Assets
ImageOptimizationState
eQuantic.UI.Images
Comece por assumir que a linha está morta. Num site, 31 de 36 ficheiros que importavam o Core não usavam nada dele. O instinto é procurar para onde cada nome foi; para a maioria dos ficheiros a resposta é lado nenhum. Apague o using, compile, e deixe o compilador nomear os poucos que precisam de algo.
Para esses, é um de quatro casos:
•
O ficheiro também importa eQuantic.UI.Primitives → apague a linha. Foram 45 de 51 ficheiros num site, e 18 de 18 nos samples do próprio SDK.
•
O ficheiro usa só um atributo de contrato e não importa Primitives → troque a linha por using eQuantic.UI.Primitives;.
•
O ficheiro usa um tipo do DOM → troque a linha por using eQuantic.UI.Web;.
•
O ficheiro importa um sub-namespace como eQuantic.UI.Core.Metadata → a tabela acima diz para onde foi. Estes são os fáceis de falhar, porque não se parecem com a linha que você procurou.
Um namespace também pode viver numa string. Se a sua app gera, faz template ou compila C# dela própria, sendo um playground ou um exemplo de código o caso habitual, o compilador não os vê. Procure também nos literais de string.
Três correções que você pode ter contornado. Uma regressão do offset de âncora da .48 está corrigida, portanto remeça quaisquer offsets que tenha afinado contra ela. O [ServerOnly] numa declaração deixa de calar a partial inteira. E um AssemblyName terminado em .App deixa de perder o executável.
Para 0.2.0-preview.48
O modelo de componentes pré-write-once desapareceu. É a maior remoção que o projeto já fez, e a primeira release em que "quebra" quer dizer alguma coisa. Se a sua app está escrita contra o vocabulário write-once — StatelessComponent/StatefulComponent do eQuantic.UI.Primitives, VisualNode, as factories — nada muda para si. Os três samples constroem sem tocar em nada.
O que foi removido, tudo do eQuantic.UI.Core:
Desapareceu
Se você usava
StatelessComponent / StatefulComponent sobre HtmlElement, ComponentState, ComponentState<T>, InputComponent<T>
Passe ao modelo write-once: o estado e o ciclo de vida vivem no COMPONENTE, portanto uma classe de estado separada vira campos mais OnMount — veja a forma em Architecture
IIconProvider
Já não há provedor. Um .svg em Assets/ vira um Vectors.Mark, e um glifo escrito à mão é uma propriedade IconGlyph — veja Ícones
SvgElement
HtmlElement("svg", …) na web, ou um Vectors.Mark para algo que tenha de funcionar nos três alvos
ComponentAttribute
Não marcava nada que o compilador lesse. Apague
Styling/Colors, Styling/Animations, Styling/EQ
Valores CSS como string, que o princípio do produto proíbe. Use ColorToken, Space, TypeRole e os canais de transição — veja Styling
A metade TypeScript foi junto: 260 linhas de core/component.ts, e o export do ComponentState. Um bundle do runtime que você tenha copiado à mão precisa de ser substituído, não remendado.
Também removidas: as três árvores de template não empacotadas em Templates/content/. Ninguém as conseguia scaffoldar (o dotnet new equantic-app sempre usou as write-once), mas estavam no repositório a ensinar o modelo errado a quem abrisse a pasta.
Porquê agora. O SDK está em preview e todos os consumidores são projetos nossos. Fazer isto depois da 1.0 significaria carregar dois modelos de componentes e um compilador que lê os dois, por causa de código que ninguém fora deste repositório alguma vez escreveu.
E três renomeações, porque o vocabulário deixou de falar a língua da web
Estas mordem uma app write-once qualquer, ao contrário de tudo o que está acima. A regra por trás delas é uma que o projeto já tinha e não tinha acabado de aplicar: a camada abstrata nomeia o que uma coisa É, não o que um alvo lhe chama.
Era
É
Porquê
Sticky
Pinned
"Sticky" é a palavra do CSS. Um nó que fica no sítio enquanto o contentor rola está fixado em todos os alvos
ZIndex (no Positioned)
Layer
Um z-index é uma coordenada do CSS. O que o nó declara é em que camada está
Alt (na Image)
Label
"Alt text" é o nome do HTML para um nome acessível. A mesma string vira accessibilityLabel nos telemóveis e um label de semântica no Photon
O compilador não ajuda aqui: são renomeações, portanto os nomes antigos simplesmente não resolvem. O Sticky é o primeiro a procurar, porque um cabeçalho fixo é a forma que quase toda app tem.
Para 0.2.0-preview.47
Nada quebra. Dito assim porque a release anterior quebrou quatro coisas e a pergunta passou a ser razoável. Tudo abaixo é superfície nova, e o código existente compila sem mudar.
O único comportamento que MUDA sem dar erro de compilação: o bundle .app de uma app macOS passa a vir do dotnet publish em vez da saída do build, e aterra dentro do diretório de publish. Um script que copiava o bundle de bin/<config>/<tfm>/ depois de um publish continua a copiar o de desenvolvimento — aponte-o para bin/<config>/<tfm>/<rid>/publish/.
Se for aqui que liga o PublishTrimmed pela primeira vez, saiba que agora é suportado e antes não era: uma app Photon com trimming abria a janela e ignorava todas as settings, em silêncio. Os avisos de trim são zero no sample; se vir algum do seu próprio código, é seu.
Para 0.2.0-preview.46
Quatro coisas deixam de compilar. As quatro têm correção de uma linha, e nenhuma delas é uma mudança de comportamento no seu próprio código.
Um ícone é um glifo; um filho é um nó
Icons.Heart já não converte sozinho para nó, então um componente que quer um FILHO quer Icon(...) à volta dele:
1
2
3
4
5
6
7
8
9
// antes
new IconButton(Icons.Heart, onPressed: Curtir)
new EmptyState(Icons.Search, "Nada aqui")
SelectedGlyph = Icons.HeartFilled
// depois
IconButton(Icon(Icons.Heart), onPressed: Curtir)
EmptyState(Icon(Icons.Search), "Nada aqui")
SelectedGlyph = Icon(Icons.HeartFilled)
Os erros são CS1503 e CS0029, e um site relatou dez deles.
A conversão foi removida em vez de corrigida porque não dava para fazer funcionar: uma conversão implícita num tipo do framework é DESCARTADA em silêncio quando o componente atravessa para JavaScript — o gêmeo declarava o tipo do glifo e o sítio da chamada emitia a string crua — então a mesma fonte construía uma coisa no Photon e outra na web. Um nó é o mesmo nó nos dois.
A factory gerada resolve capacidades sozinha
Um componente que recebe uma capacidade pelo construtor já não a recebe pela factory: a factory pergunta ao contêiner. Então a aridade cai, e o erro lê-se como se a sua própria superfície gerada tivesse perdido uma sobrecarga:
1
No overload for method 'ThemeToggle' takes 1 arguments
1
ThemeToggle(controller) → ThemeToggle()
É esse exatamente o objetivo da mudança — um site estava a passar um IThemeController por dois construtores para chegar a um botão — mas o primeiro erro que você encontra não diz isso.
Tipos só-de-servidor precisam de dizê-lo
O compilador caminha mais longe do que caminhava, então um tipo que só corre no servidor e por acaso vive no projeto web passa a falhar com EQ2004 em coisas como CSharpCompilation.Create, Stopwatch.StartNew ou LoggerExtensions.LogInformation. Marque o TIPO:
1
2
[ServerOnly]
public sealed class PlaygroundCompilation { … }
O pin do Roslyn passou para 5.9.0
O eQuantic.UI.Compiler fixa Microsoft.CodeAnalysis.CSharp 5.9.0. Um projeto que referencia Microsoft.CodeAnalysis.CSharp.Features para as suas próprias ferramentas tem de passar para 5.9.0 no mesmo commit, ou o restore falha com NU1107 — nomeando o seu projeto de testes e não o SDK.
Antes disso
Releases anteriores não estão cobertas aqui: esta página começa onde começa, em vez de fingir uma história que ninguém registou na altura.
NESTA PÁGINA
Para 0.2.0-preview.62
EQ2010: um componente chama o seu próprio membro [ServerOnly]
Uma cultura que a chamada nomeia é a da página ou a invariante
Uma data é lida na cultura da página
Um record de um projeto referenciado atravessa inteiro
Mais formas que param o build
Para quem hospeda o compilador ou escreve contra um shell
O browser responde como o .NET
Para 0.2.0-preview.61
Drawing.Width é um SizeValue
Um filho que preenche, ancorado de um lado, preenche até o outro, no Photon também
O ServerActionHub deixou de existir
Uma Server Action recusa um pedido de outra origem
Um [Authorize] na classe protege a página também
EQ1009: dois construtores que recebem o mesmo número de argumentos
Mais formas que param o build
Um provedor de completion recebe um contexto e responde uma lista
O otimizador de imagens recebe os formatos que codifica
Uma região de toque do Photon diz onde o controle é desenhado
Um peso de flex é recusado onde é escrito
O que se desenha diferente
O browser responde como o .NET
Para 0.2.0-preview.60
O IServerRenderingService recebe o Type da página
O StyleDiff é uma classe
EQ2114: um serviço que o código de browser da página usa de um jeito que o build não segue
Uma busca por uma comparação de cultura falha o build
Um dicionário construído com StringComparer.Ordinal falha o build
O browser responde como o .NET
Para 0.2.0-preview.59
Uma desconstrução posicional de CodeGrid ou de ShortcutBinding pede três variáveis
A busca do editor de código responde dentro do editor
A paleta escura ficou mais clara
Para 0.2.0-preview.58
Os tipos do editor de código foram para eQuantic.UI.Code
ListDetail recebe builders
StyleClass e o resto de eQuantic.UI.Web.Styling não existem mais
Treze ícones adotaram o nome do upstream
Um app publicado não escreve source maps
equantic.css não é mais escrito
O browser responde como o .NET, exceções incluídas
Para 0.2.0-preview.57
As declarações nativas foram para eQuantic.UI.Native.Hosting
RangeValue carrega só números
SemanticNode se desconstrói em treze partes
ImageOptimizationState não existe mais
Quatro overloads viraram um cada, e a maioria das chamadas continua compilando
EQ2012: um [ServerAction] num componente abstrato
Um IServerPrefetch composto agora roda
Para 0.2.0-preview.56
O AdjustableValue virou RangeValue
O WebFrame recebe o conteúdo como um valor só
O Navigable recebe o manipulador primeiro
As factories de gráficos espelham os seus records
O TypeStyle e o SemanticNode se desmontam na forma real
O LinkRegion e o RealizeResult carregam mais
Para 0.2.0-preview.55
ButtonStyles saiu
FlexNode é só do host
EQ2111 quando uma busca de serviço não tem por onde chavear
Um Adjustable sem valor se anuncia como group
Para 0.2.0-preview.54
A geometria mudou para Primitives
Os operadores do Point são só-do-host, e o RRect e o Matrix2D também
O ICanvasPainter recebe uma caixa e um ponto
A superfície de rotas do runtime
A superfície de serviços do RenderContext é um método só
O parâmetro do Navigator.Go é destination
O BarRect leva um Rect
Para 0.2.0-preview.53
A face de um papel é um valor por omissão, não uma escolha
Um carregamento a frio com fragmento aterra onde um quente aterra
Uma folha de cálculo existe no HTML do servidor
Uma linha cortada acaba de uma só maneira
LayoutConstraints, se hospeda o motor de layout
Para 0.2.0-preview.52
Um carregamento a frio com fragmento aterra onde um quente aterra
Para 0.2.0-preview.51
LayoutNode.Children é só de leitura — a única quebra de código
EQ2010 — um símbolo de host nomeado num componente
Um Mac em modo escuro abre a app em modo escuro
O Photon honra o Text.Align
Uma cultura regional cai pelo PAI
Variant.Link carrega a tinta em Base e Pressed
Estilo web: o papel estiliza a classe, o nó estiliza o elemento
Para 0.2.0-preview.50
O IWorkspace.OpenUrl só entrega os esquemas que a sua app abre
O VisualNode.Key passa a fazer o que sempre disse que fazia
O scroll do foco salta em vez de deslizar
Duas correções de layout que podem mexer em algo
O eQuantic.UI.Charts é outro pacote
Para 0.2.0-preview.49
Para 0.2.0-preview.48
E três renomeações, porque o vocabulário deixou de falar a língua da web
Para 0.2.0-preview.47
Para 0.2.0-preview.46
Um ícone é um glifo; um filho é um nó
A factory gerada resolve capacidades sozinha
Tipos só-de-servidor precisam de dizê-lo
O pin do Roslyn passou para 5.9.0
Antes disso