NETReporter 1.2.0

NETReporter

Knihovna pro generování reportů (PDF, SVG, HTML, JSON) z deklarativních JSON šablon a JSON dat.

Šablona popisuje stránku, sekce, rozvržení, styly a bindingy na data; knihovna ji zvaliduje, vyhodnotí nad daty a vykreslí. PDF a SVG kreslí QuestPDF, grafy OxyPlot.

Instalace

Balíček je v privátním NuGet feedu Advisor Software:

dotnet nuget add source https://nuget.advisor-soft.com/v3/index.json --name AdvisorSoft
dotnet add package NETReporter

Požadavky: .NET 10.

Rychlý start

using NetReporter;

var renderer = new ReportRenderer();

await using var template = File.OpenRead("template.json");
await using var data = File.OpenRead("data.json");
await using var pdf = await renderer.RenderAsync(template, data);   // výchozí formát je PDF

await using var file = File.Create("report.pdf");
await pdf.CopyToAsync(file);

S DI kontejnerem (ReportRenderer se registruje jako singleton):

services.AddNetReporter();                        // ReportRenderer, TemplateSchemaParser, TemplateAnalyzer, VisualTreeBuilder
services.AddNetReporter<MyPipelineObserver>();    // + vlastní IReportPipelineObserver

var renderer = serviceProvider.GetRequiredService<ReportRenderer>();

RenderAsync bere šablonu a data jako Stream a vrací Stream s výstupem (pozice 0). Při neplatné šabloně vyhodí InvalidOperationException s diagnostikami (zpráva + JSON cesta).

Šablona v kostce

{
  "version": "1.0",
  "meta": { "name": "Faktura", "culture": "cs-CZ", "palette": { "primary": "#1A73E8" } },
  "page": { "size": "A4", "orientation": "Portrait", "marginsMm": { "top": 15, "right": 12, "bottom": 15, "left": 12 } },
  "styles": {
    "base": { "font": "Arial", "fontSize": 9 },
    "h1": { "fontSize": 16, "bold": true, "color": "primary" }
  },
  "content": {
    "root": {
      "type": "Stack",
      "gapMm": 3,
      "items": [
        { "type": "Text", "text": "Faktura {{$.Number}}", "styleRef": "h1" },
        { "type": "Text", "text": "Vystaveno: {{$.Issued:dd.MM.yyyy}}, celkem {{$.Total:C2}}" },
        {
          "type": "Table",
          "columns": [
            { "key": "name", "header": "Položka", "width": "3*" },
            { "key": "price", "header": "Cena", "width": "1*", "align": "Right" }
          ],
          "rows": {
            "source": "$.Items",
            "as": "item",
            "cells": {
              "name": { "text": "{{item.Name}}" },
              "price": { "text": "{{item.Price:N2}}" }
            }
          }
        }
      ]
    }
  },
  "pageFooter": {
    "root": { "type": "PageNumbers", "mode": "Current", "showTotal": true, "separator": " / " }
  }
}
  • Sekce: pageBackground, pageHeader, reportHeader, content, reportFooter, firstPageFooter, pageFooter, pageOverlay.
  • Rozvržení: Stack, Row, Box, Absolute, Spacer, PageBreak; libovolný uzel lze opakovat nad kolekcí přes "repeat": { "source": "$.Items", "as": "item" }; "keepTogether": true (u Stack, Row, Box, Text, Paragraph, Table) zabrání zalomení uzlu přes stránku – nevejde-li se na zbytek stránky, přesune se celý na další.
  • Obsah: Text, Paragraph, HttpLink, Image, Line, PageNumbers, Barcode, Code2D (QR, DataMatrix…), Checkbox, Table (skupiny, souhrny, stránkování), Chart.
  • Binding: {{$.Cesta.K.Hodnote}} (kořen dat), {{alias.Pole}} (položka repeat/tabulky/grafu), formát za dvojtečkou ({{$.Total:N2}}, {{$.Date:dd.MM.yyyy}}), C# výrazy s prefixem # ({{#item.Qty * item.Price}}), systémové proměnné now, page.number, page.total. Formátuje se podle meta.culture.
  • Styly: pojmenované styly v styles (base platí pro vše), odkaz styleRef a inline style; písmo, barvy (#RRGGBB nebo název z meta.palette), okraje a výplně v mm, rámečky, zarovnání. Styl lze vybrat podle dat – styleRef může obsahovat binding, jehož výsledkem je jméno stylu ("styleRef": "{{# item.Overdue ? \"overdue\" : \"\" }}"); tabulky mají styl řádku rows.styleRef a buňky rows.cells.<key>.styleRef / style.

Kompletní popis formátu je v přibalené dokumentaci (viz níže) – kapitola 02_template_format.md.

Grafy

{
  "type": "Chart",
  "chartType": "Column",
  "source": "$.Months",
  "as": "m",
  "category": "{{m.Month}}",
  "series": [
    { "name": "2025", "value": "m.Revenue", "color": "primary" },
    { "name": "Plán", "value": "#m.Revenue * 1.1", "dashed": true }
  ],
  "valueAxis": { "format": "N0" },
  "legend": { "position": "Top" }
}

Typy Column, Bar, Line, Area, Pie, Donut; více sérií, paleta, legenda, osy, popisky hodnot, skládání. Kombinovaný graf: série může přepsat typ ("type": "Line") a čáry mohou mít druhou osu vpravo ("axis": "Secondary" + secondaryValueAxis). Grafy jsou vektorové ve všech výstupech. Podrobnosti: 02_template_format.md §7.11 a 17-charts.md.

Výstupní formáty

await using var output = await renderer.RenderAsync(template, data, new RenderOptions { Format = RenderFormat.Svg });
Formát Popis
RenderFormat.Pdf výchozí; plnohodnotné PDF se stránkováním
RenderFormat.Svg stejný layout a stránkování jako PDF; jeden SVG se stránkami pod sebou (data-pages, <svg data-page="N">)
RenderFormat.Html HTML náhled bez stránkování (vhodný pro editor/prohlížeč)
RenderFormat.Json strukturovaný výsledek renderu – vyhodnocené texty, data grafů, metadata obrázků a kódů; pro automatické a AI kontroly

Každou SVG stránku zvlášť vrací RenderSvgPagesAsync:

IReadOnlyList<string> pages = await renderer.RenderSvgPagesAsync(template, data);

Do SVG se nevkládají fonty – prohlížeč použije písmo nainstalované v systému (pozice znaků zůstanou zachované).

Ladění šablon: new RenderOptions { Format = RenderFormat.Html, RenderBindingsAsExpressions = true } zobrazí místo hodnot samotné binding výrazy.

Limit stránek: PDF/SVG render skončí RenderPageLimitExceededException po překročení RenderOptions.MaxPages (výchozí 10 000, 0 = bez limitu) – ochrana před šablonou se zacykleným layoutem. CancellationToken se kontroluje i během generování stránek. Generování PDF/SVG (QuestPDF) se v rámci procesu serializuje (souběžné generování QuestPDF tiše poškozuje výstup), takže souběžné požadavky na PDF/SVG čekají na řadu; HTML a JSON se renderují souběžně.

Metadata PDF

Vlastnosti dokumentu (Název, Autor, Předmět, Klíčová slova, Aplikace, Jazyk) se nastavují v meta.document. Hodnoty jsou texty s bindingy vyhodnocené nad daty; prázdný výsledek se nezapíše, klíčová slova mohou být pole a bez language se použije meta.culture. Metadata se zapisují jen do PDF.

"meta": {
  "name": "Faktura",
  "culture": "cs-CZ",
  "document": { "title": "Faktura {{$.Number}}", "author": "{{$.Supplier}}", "keywords": [ "faktura", "{{$.Number}}" ] }
}

Podrobnosti: 02_template_format.md §1.4.

Validace šablony

Šablonu lze zkontrolovat bez renderu. Parser ji ověří proti vestavěnému schématu a vrátí diagnostiky s JSON cestou k chybě:

using NetReporter.Common;
using NetReporter.Template.Parsing;

var parser = new TemplateSchemaParser();                  // výchozí ParserValidationMode.Strict
var result = parser.Parse(templateJson);

if (!result.Success)
{
    foreach (var d in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
        Console.WriteLine($"{d.JsonPath}: {d.Message} ({d.Code})");
}
  • Strict hlásí neznámé vlastnosti a chybný obsah stylů (neznámá vlastnost, špatný typ, neplatný enum) jako chybu, Compat jako varování (new TemplateSchemaParser(validationMode: ParserValidationMode.Compat)).
  • Diagnostiky mají stabilní Code a JsonPath (např. $.content.root.items[2].rows.source).
  • Schéma šablon (typy uzlů, vlastnosti, enumy) vrací TemplateSchemaProvider.Create().

Podrobnosti: kapitola 03_parser_and_validation.md.

Sledování pipeline

IReportPipelineObserver dostává události OnPipelineStarted, OnParseStarted/Completed, OnRenderStarted/Completed (doba, velikost výstupu) a OnError (fáze, kategorie, stabilní kód chyby) – vhodné pro metriky a logování. Registruje se přes AddNetReporter<TObserver>() nebo konstruktor ReportRenderer. Knihovna loguje přes ILogger, pokud předáte ILoggerFactory.

RenderCompletedContext v OnRenderCompleted nese také BindingDiagnostics a OmittedBindingDiagnostics: chyby v bindingech (nedokončené {{, nečíselná hodnota grafu…) render nezastaví, takže je jinak neuvidíte. Shodné diagnostiky se slučují a počet omezuje RenderOptions.MaxBindingDiagnostics (výchozí 200, 0 sběr vypne).

Dokumentace v balíčku

Balíček obsahuje dokumentaci knihovny a šablon ve složce docs/ (po instalaci v %USERPROFILE%\.nuget\packages\netreporter\<verze>\docs\, na Linuxu ~/.nuget/packages/netreporter/<verze>/docs/). Rozcestník je docs/README.md:

Soubor Obsah
docs/user/pouziti-knihovny.md průvodce použitím knihovny krok za krokem
docs/dev/02_template_format.md formát šablony – sekce, uzly, vlastnosti, repeat, tabulky, grafy
docs/dev/03_parser_and_validation.md parser a validace šablon, diagnostiky
docs/dev/04_style_system.md stylovací systém
docs/dev/05_binding_system.md binding a výrazy
docs/dev/17-charts.md grafy – chování a omezení
docs/dev/01_introduction.md, 06_runtime_pipeline.md, 07_renderer_questpdf.md, 08_program_flow_example.md, 09_extensibility.md architektura knihovny, pipeline, renderer, rozšiřitelnost

Seznam změn všech verzí je v souboru CHANGELOG.md v kořeni balíčku; změny konkrétní verze ukazuje NuGet v release notes balíčku.

Licence

MIT, © Advisor Software s.r.o.

Zdrojový kód, ukázkové šablony a změny verzí: repozitář projektu.

No packages depend on NETReporter.

- New validation warnings: `NRP026` for a static `styleRef` (also the items of `styles[]`, `spans[].styleRef`, the table style properties and the `styleRef` of table cells) that names a style missing from `styles`, and `NRP027` when `styles.base` is missing. Values containing a binding (`{{...}}`) are skipped because the style name is only known at render time. Both are warnings and do not make a template invalid. - Validation: the `NRP020` / `NRP021` conflict check (`marginMm` together with `marginTop/Right/Bottom/LeftMm`, `paddingMm` together with `paddingTop/Right/Bottom/LeftMm` in one style object) now also covers the inline `style` of table cells (`rows.cells.<key>.style`, `rows.group.summary.cells.<key>.style`, `totalSummary.cells.<key>.style`) and of paragraph spans (`spans[].style`); before it applied only to `styles.*` and the inline `style` of nodes. The diagnostic path points at the style object, e.g. `$.content.root.rows.cells.status.style`. It is an error like on nodes, so a template that combines them in a cell or span style no longer validates and is not rendered: keep either the all-sides or the per-side properties. Combining them across layers (for example `styles.base` with `paddingMm` and an inline `style` with `paddingTopMm`) is still allowed and the per-side value wins for that side. - Binding diagnostics (an invalid expression, including one in a bound `styleRef`) were collected and silently dropped. `TemplateRuntimeEngine` now writes them to its logger (`Error` / `Warning`, with the JSON path of the expression); the metadata bindings of the PDF document are logged as well. - Template editor: the global Delete key handler deleted the selected template node even while typing in a text box (for example clearing a table cell text removed the node); key presses from inputs, text areas, selects and editable elements are now left to the focused element. - Template editor: editing any property in the generic property grid no longer turns array properties (`Text.styles`, `HttpLink.styles`, `Row.columns`, style `fontFallbacks` / `fontFeatures`) into a JSON string that the parser silently ignored; the array is written back as an array, and text that is not a JSON array is reported as an error instead of being saved. - Fix: a table cell (`rows.cells`, group summary, `totalSummary`) with an empty `text` made the parser drop the whole table without any diagnostic. An empty `text` is now valid (blank cell, cell with only `styleRef`/`style`); a missing `text` is reported as `NRP011` and a non-string `text` as `NRP014` at the cell path. - Fix: named style layers no longer reset the values of earlier layers back to `styles.base`. `styleRef` followed by `Text.styles[]` / `HttpLink.styles[]` items, a paragraph `spans[].styleRef` over the paragraph style, and the table group `headerStyleRef` / `summaryStyleRef` and `totalSummaryStyleRef` over `cellStyleRef` merged the precomputed `base + style`, so e.g. base `fontSize` 9, `styleRef` with `fontSize` 14 and `styles: ["bold"]` rendered at 9. Each layer now overrides only the properties the style itself defines. A named style with a border and no padding still gets the default padding. - Fix: binding diagnostics of table summary cells carried the path `{table}.summary.cells.{key}`; they now use `{table}.rows.group.summary.cells.{key}` (group summary) and `{table}.totalSummary.cells.{key}` (total summary). - Template editor: renaming a table column key moves its cell mapping (text, `styleRef`, `style`) in the rows and both summaries to the new key instead of leaving it under the old one; a cell with only a style is saved (with an empty text) instead of being dropped; cell read/write moved to the public `TableCellsModel` helper with unit tests. - Template editor: the table editor model (columns, row source and row style, cell mapping, styles, pagination, group, total summary) and its JSON mapping moved from the `TableEditor` component into the public, unit-tested `TableEditorModel` (`Load` / `LoadIfChanged` / `Apply` and the column operations); behaviour is unchanged except for edge cases: unknown properties of table columns and of `pagination` are kept when the table is saved, a new column gets the first free key `colN` (after removing a middle column the generated `col3` could repeat an existing key, which the parser reports as `NRP028`), removing a column keeps the cell mappings that another column with the same key still uses, and a value of the wrong type in the table JSON (for example `"key": 5` loaded through "Načíst z JSON") no longer throws while the editor loads it. - Docs: fixed the duplicate section numbers in `02_template_format.md` (`PageBreak` is 6.6, `Barcode` 7.8, `Code2D` 7.9, `Checkbox` 7.10, `Chart` 7.11) and the unclosed code fence in the root `README.md`. - Fix: a table that could not be built was dropped by the parser without any diagnostic, so validation reported `success=false` with zero errors (and in an optional section such as `pageHeader` the table was silently omitted). It is now reported with the JSON path: new error `NRP028` (`DiagnosticCodes.DuplicateTableColumnKey`) for a duplicate column `key` (compared case-insensitively, reported on the later column), `NRP011` for an empty or missing column `key`, `rows.source`, `rows.as`, `rows.group.by`, or a summary (`rows.group.summary`, `totalSummary`) without `cells`, and `NRP014` for a cell or summary `cells` that is not an object (for example the shorthand `"status": "{{x}}"`). The table diagnostics carry the node id of the table, so the template editor marks the table node. A template with a broken table in an optional section now fails validation instead of rendering without the table. - A table column without `header` or `width` (both optional in the schema) is now valid instead of making the parser silently drop the whole table: a missing or empty `header` renders an empty header cell, a missing or empty `width` falls back to `1*`. This also fixes tables after "Add column" in the template editor, which creates a column with an empty header. - A missing table cell for a column (`NRP022`) is no longer reported twice when two columns share the same key. - Validation: the content of styles is now checked against `styleProperties` of the template schema - the named styles in `styles` and every inline `style` (nodes, `Paragraph` `spans[].style`, table cells incl. group and total summaries). An unknown property (for example the typo `colour`, with a "Did you mean" hint; property names are case-sensitive), a value of the wrong JSON type (for example `"fontSize": "12"`), an invalid enum value (for example `"align": "Centre"`), an array of strings with a non-string item, the same problems inside `border*` / `shadow`, and a `styles` or a named style that is not an object are reported as `NRP013` / `NRP014` / `NRP015` / `NRP016` with the JSON path of the value (inline styles carry the node id). It is an error in `Strict` mode (the default, also used by `ReportRenderer`, ReportServer and MCP `validate_template`) and a warning in `Compat` mode. Before, the parser silently dropped such values. `null` values are accepted, colors may be hex or `meta.palette` names and their format is not checked. - Behavior change: a template with a mistake in a style that used to validate and render (the value was silently ignored) now fails strict validation and is not rendered. This includes undocumented enum aliases that the renderer tolerated (`Middle`, `End` for alignment, `NormalWeight` for `fontWeight`); use the values from the schema (`Center`, `Bottom` / `Right`, `Normal`). Validate in `Compat` mode to list all such places as warnings. - Schema: added the missing style properties `cellPaddingTopMm`, `cellPaddingRightMm`, `cellPaddingBottomMm` and `cellPaddingLeftMm` (supported by the renderer, but absent from `template-schema.json`, so the template editor did not offer them). The types and nested members of `styleProperties` are now validated when the schema is loaded (unknown type, duplicate name). - Samples: `prohlaseni-mlcenlivost` no longer sets `firstLineIndentationMm` in the named style `bodyText` (it is a `Paragraph` property, ignored in a style; every paragraph using the style sets its own value). - Behavior change: a `totalSummary` cell of the column key `qty` whose text is empty (or a binding that evaluates to empty) no longer shows the row count. The undocumented rule is removed, so an empty text now always renders an empty cell, as in every other column and in group summaries; to show a row count write it explicitly, e.g. `{{# root.Invoice.Items.Count }}`. The same applies to ASTs built in code (`TableCellNode { Text = "" }` or an unset `Text` in `TableNode.TotalSummary`). - Hardening: `BindingRuntime.EvalString` never returns `null`; a custom `IBindingEvaluator` that violates the non-null contract of `Render` now yields an empty string plus a `BindingDiagnostic` warning with the JSON path instead of a `NullReferenceException` (bound `styleRef`, table group key) or a `null` passed to the render sink. - Binding diagnostics are now returned to the caller: `RenderCompletedContext` (`IReportPipelineObserver.OnRenderCompleted`) has the new init properties `BindingDiagnostics` (`IReadOnlyList<BindingDiagnostic>`, empty when there is none) and `OmittedBindingDiagnostics`. They cover PDF, SVG (including `RenderSvgPagesAsync`), HTML and JSON, but only what the format renders: HTML and JSON do not render the sections `pageBackground`, `firstPageFooter` and `pageOverlay` (an empty list from them says nothing about those sections), and the metadata bindings (`meta.document`) are evaluated only for PDF; render PDF or SVG for a complete check. The constructor, `Deconstruct` and `IReportPipelineObserver` are unchanged, so existing observers keep compiling; `RenderAsync` still returns only the stream. Diagnostics are not added to the JSON/HTML output. - New `RenderOptions.MaxBindingDiagnostics` (default `DefaultMaxBindingDiagnostics` = 200): identical diagnostics (same severity, message and JSON path) are merged, distinct ones beyond the limit are only counted in `OmittedBindingDiagnostics` (a render with thousands of rows stays bounded; it counts occurrences, a repeated omitted diagnostic is counted each time) - once the limit is reached an `Error` replaces the newest less severe diagnostic (for example a `Warning`), so a flood of warnings such as many different non-numeric chart values cannot hide a later error; 0 or a negative value turns the collection off (diagnostics are still logged). - Binding diagnostics are now logged once per distinct diagnostic per render (a chart series value with an invalid expression used to log one line per data row), and one warning is logged when the limit is reached. - MCP server: `render_report` and `render_report_from_files` return `bindingDiagnostics` in the structured result (severity, message, JSON path; at most 50, with a final `Info` entry that says how many more occurrences were omitted) and mention the count in the text summary. The new property is nullable in the output schema (an empty array means none for the rendered parts, `null` means the output was rendered by ReportServer); the tool descriptions state that `format=json` / `html` do not render `pageBackground`, `firstPageFooter` and `pageOverlay`, so use `pdf` or `svg` for a complete binding check. - Docs: binding diagnostics in the binding system and runtime pipeline chapters, the library usage guide (pipeline observer, error handling table: an invalid binding does not throw) and the package README. - Template editor: Undo / Redo for every change of the template (property grid, table and chart editor, add / delete / copy / paste / drag-drop, page settings, styles, JSON tab, reset, load, upload): toolbar buttons and Ctrl+Z, Ctrl+Y / Ctrl+Shift+Z outside text inputs (inside a text box Ctrl+Z stays the native undo of the text). The last 50 versions (at most about 4 million characters) are kept in memory of the editor session; text typed into the JSON tab and not parsed yet becomes a step of its own (before every add / delete / paste / drag-drop / Undo / Redo and when another tab is selected), so neither the operation nor Undo loses it. - Template editor: protection of a valid template: a change that makes a valid template invalid (errors, or the tree cannot be built) stays applied but a banner offers "Vrátit zpět" (Undo); a change that would silently drop template nodes beyond the deleted ones is rejected and the template stays unchanged. Adding a default node (whose `Paragraph`, `Box` and `Absolute` are incomplete until filled in), replacing the whole template (reset, load, upload) and hand-written JSON are checked less strictly, see `13-editor-visual-tree.md`. - Fix (template editor): one key press deleted two nodes (the selected node and then its parent, e.g. the whole `content.root`, leaving `{ "id": ... }` and `NRP009`) because the keyboard and preview JS modules were initialised twice on the first render (two concurrent `OnAfterRenderAsync` calls) and registered two listeners. Initialisation is now single-flight (`EditorJsInterop`), the JS `init` is idempotent, and a duplicate event while a delete / add / paste / undo is running is ignored. - Template editor: the Delete key ignores auto-repeat, a key-triggered delete or undo refreshes the UI (the tree stayed stale until the next event), the root node of a section (`content.root` ...) can no longer be deleted (it always produced an invalid template; delete the section or edit the node), and after a delete the parent is selected from the new tree instead of a detached node of the old one. After an edit the selected node is always looked up in the new tree (a section has a stable id derived from its name, e.g. `section_content`; it used to get a new random id on every analysis, so Undo / Redo with a section selected lost the selection) instead of keeping a detached node of the old tree. Saving a style now also refreshes the diagnostics. - Fix: concurrent PDF / SVG generation silently corrupted the output. QuestPDF 2025.12.3 is not safe for concurrent generation in one process: SVG pages lost texts (for example a page without its heading, in about half of the concurrent renders of a keepTogether template) and the text mapping of PDFs (copy / search) broke, even between different `ReportRenderer` instances and in the released 1.1.1 (`ReportRenderer` is a DI singleton in ReportServer and the MCP HTTP server renders for parallel clients). `ReportRenderer` now serializes `GeneratePdf` and `GenerateSvg` per process (a static `SemaphoreSlim`; waiting for the turn honours the `CancellationToken`); HTML and JSON rendering stay concurrent. Trade-off: PDF / SVG throughput of one process no longer grows with the number of threads. - Validation: a `Table` without columns (`columns: []`, or the property omitted, which the schema fills with `[]`) is now reported as `NRP011` at `<table path>.columns` (both validation modes). QuestPDF cannot render a table without columns, so PDF / SVG used to fail at render time with a `DocumentComposeException` although validation passed. - Schema: `Table.rows.as` is `required` with the default `""` like `rows.source` (it was optional, although the parser has always required it and a missing or empty `as` is `NRP011`), so `get_template_schema` and the editor property grid no longer present it as optional. - Behavior change: a style `border` / `borderTop` / `borderRight` / `borderBottom` / `borderLeft` without `width` (for example `{ "color": "#FF0000" }`) is now drawn with the schema default width `1`; it used to get width `0`, i.e. an invisible border, although the schema and the documentation say `1`. Use `"width": 0` for an invisible border. - Validation: the `NRP020` / `NRP021` diagnostics for node, table cell and paragraph span styles now carry the `NodeId` of the owning node (the template editor marks the node; `styles.*` has no node), and a property with the JSON value `null` is treated as omitted (`{ "marginMm": null, "marginTopMm": 2 }` is no longer a conflict), as documented for the other style checks. - Fix: a static style name with leading or trailing whitespace (`"styleRef": "big "`, items of `styles[]`, `spans[].styleRef`, `headerStyleRef`...) was silently not applied although the `NRP026` check trimmed the name and found the style, while the same string worked in table `rows.styleRef` / `cells.*.styleRef`. `StyleResolver` now trims style names everywhere. - Fix: with `RenderOptions.RenderBindingsAsExpressions` (the editor preview) a chart series `value` containing a binding produced a false binding diagnostic "cannot convert to a number" (the echoed expression is not a number); it is not reported in that mode. - Template editor: the Delete key and Ctrl+Z / Ctrl+Y are ignored while a dialog is open (for example "Načíst šablonu" or the save prompt) and the focus is on the dialog header, text or a button; they used to change the template behind the dialog. Text typed into the JSON tab is also kept when a tree operation or another tab follows (see the Undo / Redo bullet). - Template editor: the `/Error` page no longer fails with HTTP 500 (it was based on an abstract `PageModel` and a missing layout); it is a standalone page with the Czech error text. Celý seznam změn: CHANGELOG.md v balíčku a https://dev.azure.com/advsoft/Knihovny/_git/NetReporter?path=/src/NetReporter/CHANGELOG.md

Version Downloads Last updated
3.9.2 3 10/28/2026
3.9.1 1 10/27/2026
3.9.0 6 10/20/2026
3.8.1 6 10/19/2026
3.8.0 4 10/19/2026
3.7.0 4 10/19/2026
3.6.0 6 10/17/2026
3.5.1 62 10/15/2026
3.5.0 4 10/15/2026
3.4.2 5 10/15/2026
3.4.0 4 10/12/2026
3.3.0 5 10/11/2026
3.2.2 5 10/10/2026
3.2.1 4 10/10/2026
3.2.0 4 10/10/2026
3.1.1 4 10/09/2026
3.1.0 5 10/09/2026
3.0.1 8 10/08/2026
3.0.0 5 10/08/2026
2.2.1 5 10/05/2026
2.2.0 4 10/05/2026
2.1.0 9 10/04/2026
2.0.1 8 10/04/2026
2.0.0 5 10/03/2026
1.7.0 6 10/03/2026
1.6.0 5 10/02/2026
1.5.0 12 10/02/2026
1.4.0 6 10/01/2026
1.3.0 4 10/01/2026
1.2.0 5 10/01/2026
1.1.1 6 09/30/2026
1.1.0 8 09/30/2026
1.0.10 4 09/29/2026
1.0.9 4 09/29/2026
1.0.8 16 09/29/2026
1.0.7 115 09/24/2026
1.0.6 9 09/24/2026
1.0.5 7 09/24/2026
1.0.4 11 09/24/2026
1.0.3 8 09/24/2026
1.0.2 13 09/23/2026
1.0.0 7 09/23/2026
0.9.1 13 09/23/2026
0.9.0 87 09/14/2026
0.8.1 25 02/20/2026
0.8.1-preview.1 34 02/13/2026
0.7.1-preview.2 32 02/10/2026
0.7.1-preview.1 33 02/10/2026
0.7.0 30 02/10/2026
0.0.6 28 02/01/2026
0.0.5 19 02/01/2026
0.0.0-alpha.0.17 35 02/01/2026