quario 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,15 +1,178 @@
1
- # Changelog
1
+ # quario
2
+
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **BREAKING: an image whose header does not state its size now fails on every
8
+ target.** A PNG or JPEG truncated before its dimensions used to render on the
9
+ HTML and CSV targets — the browser coped, and CSV has no picture to place —
10
+ while failing on the PDF and XLSX targets, which have to know how big a
11
+ picture is before they can put it anywhere. One file, two verdicts, depending
12
+ on where the report was sent.
13
+
14
+ It is now a single render error, raised where every other verdict on image
15
+ bytes is raised and naming the item's `source` the same way — for example
16
+ `header[0].source [=$.input.logo]: could not read the image's size from its bytes`.
17
+ A report that renders as a PDF today is unaffected: those bytes already
18
+ failed there. What changes is a report that renders as HTML today on bytes no other target
19
+ would accept: it now fails, and the file it names is one no target could ever
20
+ have drawn correctly.
21
+
22
+ Nothing further has changed about what the engine promises: a file whose size
23
+ reads but whose pixel data is corrupt is still nobody's guarantee, and each
24
+ target does whatever its own machinery does with it.
25
+
26
+ - **The image event carries the picture's size.** `width` and `height`, in
27
+ pixels, read from the same header the format is sniffed from. A target — the
28
+ built-in ones, or your own — places a picture from two fields instead of
29
+ parsing a PNG's IHDR and a JPEG's frame header for itself.
30
+ - **A page-number token says which page value it is.** A value token whose
31
+ interpolation is exactly `{{ page.number }}` or `{{ page.total }}` now carries
32
+ `field` on the event stream, holding that same string. The `value` is
33
+ unchanged — the number the page band was called with — so nothing renders
34
+ differently; `field` tells a target whose own document format numbers pages
35
+ that it may write its own live field there instead of the number this render
36
+ saw. Anything computed from them (`{{ page.number + 1 }}`, `{{ pad(page.total)
37
+ }}`) is an ordinary value and carries no `field`, so write the bare token
38
+ wherever a live number matters.
39
+
40
+ ### Patch Changes
41
+
42
+ - **A PNG wider or taller than 65535 pixels is no longer misplaced.** Its
43
+ dimensions were read from the low half of each of IHDR's four-byte fields, so
44
+ a 70000-pixel panorama measured 4464 pixels and was laid out at that size,
45
+ confidently and silently.
46
+ - **Every published README says where the documentation is.** Each package now
47
+ carries a Documentation section pointing at the reference, at the report schema
48
+ that normatively specifies what a report may declare, and at the package's own
49
+ API. The paragraphs that used to end on an unstated contract — the event
50
+ stream's field semantics, the style vocabulary, page columns, the Content
51
+ Security Policy a fragment with images needs, the formula mangling, and each
52
+ target's own contract — link the page that states it. Every link is an absolute
53
+ URL, so it resolves from the npm package page as readily as from an installed
54
+ copy.
55
+
56
+ ## 0.7.0
57
+
58
+ ### Minor Changes
59
+
60
+ - **Styled runs.** Bold a word, colour a phrase, or format one value inside a
61
+ sentence, and have it survive into the PDF and the spreadsheet — not only the
62
+ HTML, which was the only place `<b>` in a template ever meant anything. A run
63
+ carries the inline declarations only: no padding, borders or spacing, which
64
+ belong to a whole line or block.
65
+
66
+ A cell's tokens carry a run's resolved style on the tokens it covers, and
67
+ `styledRuns(tokens)` groups them back out — the same rule every built-in
68
+ target reads them through, so a custom target cannot drift from them.
69
+ `RUN_STYLE_NAMES` is the inline half of the vocabulary as a list, beside
70
+ `STYLE_NAMES`, for a tool that offers an author what a run may wear.
71
+
72
+ - **`plan()` now reports declarations that nothing will read.** A definition can
73
+ be perfectly valid and still say something the engine quietly drops, and
74
+ until now the only way to find out was to notice the output was wrong.
75
+ `plan()` returns a `warnings` list beside `problems` — same shape, same
76
+ document order, one entry per declaration — and it currently catches three:
77
+
78
+ - a `format` on a cell whose value can never present a single interpolation —
79
+ `"Due {{ due }}"` under `format: "date"` — which the engine leaves unread;
80
+ - a `currency` on a cell whose `format` is not `"currency"`, which the engine
81
+ reads only under that kind and otherwise ignores, so the cell renders in
82
+ the instance's currency and the code you wrote does nothing;
83
+ - a table where every column declares a `width` and the widths total under
84
+ 100, which leaves the trailing share of the table unused — usually a column
85
+ edited down without its neighbours being adjusted.
86
+
87
+ `problems` is unchanged and still fatal: a definition with problems compiles
88
+ to nothing, while one carrying only warnings compiles and renders exactly as
89
+ before. Hosts that ignore the new field are unaffected, and `validate()` is
90
+ untouched.
91
+
92
+ Warnings are advisory and deliberately incomplete — they catch what can be
93
+ seen in the definition alone, without data, so a quiet `warnings` list is not
94
+ a promise that every declaration will be read.
95
+
96
+ - **A `format` declaration now needs one value to speak about, and a cell can
97
+ give it one.** Previously a `format` on a cell reached every interpolation in
98
+ it and skipped the literal text between them, which meant
99
+ `"{{ amount }} of {{ quantity }}"` under `format: "currency"` rendered the
100
+ quantity as money too — and the page and the spreadsheet disagreed about it,
101
+ the page formatting inside a sentence where the sheet did not.
102
+
103
+ A cell `value` may now be a list of **styled runs** —
104
+ `{ "value": "…", "style": { … } }` — each with its own declarations, and
105
+ `format` applies to a run holding a single interpolation.
106
+
107
+ **What changes in an existing report:** a cell that mixes text and an
108
+ interpolation under a `format` stops presenting that value and renders it
109
+ plainly, in every target. `"Due {{ due }}"` with `format: "date"` was
110
+ `Due 14 Aug 2026` on the page and `Due 2026-08-14` in the sheet; it is now
111
+ the plain form in both. To restore it, split the value and leave the
112
+ declaration where it is:
2
113
 
3
- All notable changes to quario are documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [0.6.0] - 2026-09-07
114
+ ```jsonc
115
+ {
116
+ "value": [{ "value": "Due " }, { "value": "{{ due }}" }],
117
+ "style": { "format": "date" }
118
+ }
119
+ ```
11
120
 
12
- ### Added
121
+ A cell's `format` reaches its runs, so nothing moves and nothing is declared
122
+ twice. `plan()` reports the cells this affects wherever it can see them
123
+ without data.
124
+
125
+ - **`round`, `floor` and `ceil` reuse their formatters instead of rebuilding
126
+ one per call.** The three reach the decimal you wrote through `Intl`, and
127
+ each call built a fresh formatter to do it — so a detail column of
128
+ `{{ round(@.qty * @.price, 2) }}` paid for one per row. A hundred-thousand
129
+ row report with two such columns drained in 3.5 s; it now drains in 0.2 s.
130
+ Every answer is the value it was before: the rounding mode and the digit
131
+ count are both part of what a reused formatter is found by, so no call can
132
+ be handed one built for another.
133
+ - **`round`, `floor` and `ceil` name themselves when they refuse an `n`.** All
134
+ three threw `n must be a number from 0 to 100`, so a cell calling more than
135
+ one was told how it failed and never which call; the message now opens with
136
+ the function, as a reducer's already did. The located path still names the
137
+ cell — one cell holds as many calls as you write, which is the whole reason
138
+ the name is worth having.
139
+
140
+ **An `n` that cannot be read as a number at all now gets the same answer.**
141
+ A `BigInt` or a `Symbol` reaching `n` from render data used to surface the
142
+ runtime's own `Cannot convert a BigInt value to a number`, and an object
143
+ carrying a throwing `valueOf` surfaced whatever it threw — each located at
144
+ the cell but naming neither the function nor anything an author could act
145
+ on. All of them are now the same refusal.
146
+
147
+ - **`isDiagnostic` is documented for what it actually answers.** The README
148
+ said it was true for "one of the stack's located errors: quario's own, or one
149
+ thrown directly by xprsn, sjabloon, or padvinder", and its example read a
150
+ `false` as "one of your own functions failed". Only an engine's own error is
151
+ a diagnostic: quario's verdicts on a document — an unknown reducer, a
152
+ misused inline reducer, image bytes it will not vouch for — are located just
153
+ the same and are not diagnostics, so a host following that example rethrew
154
+ report faults as its own. Nothing about the guard changes; it never behaved
155
+ the way the page described.
156
+ - **A misused inline reducer says what it is, instead of how it folds.**
157
+ `{{ min(1, 2) }}` — the two-argument scalar `min` quario does not have —
158
+ failed at the cell with `(rows || []).map is not a function`, the fold's own
159
+ internals, naming neither the reducer nor the mistake. It now reads
160
+ `min is a reducer over an array, not a scalar function; got a number`, and a
161
+ second argument that is not a lambda says so in the same words rather than
162
+ reaching a host as `of is not a function`. Register a function of your own to
163
+ shadow a built-in reducer when a report needs the scalar.
164
+
165
+ **Two reducers used to answer instead of failing**, each reading a `length`
166
+ off a value that is not a collection: `count('ab')` presented `2` — a
167
+ string's own length, folded as if it were a count of rows — while `count(1)`
168
+ and `avg(1, 2)` presented nothing at all. All three are now the same located
169
+ error. A reducer over an **absent** array is unchanged and still not a
170
+ mistake: `sum(@.lines)` on a row carrying no lines is `0`, exactly as an
171
+ empty array gives.
172
+
173
+ ## 0.6.0
174
+
175
+ ### Minor Changes
13
176
 
14
177
  - **`imageError(path, said, cause)`**, the mint a render target raises an image
15
178
  failure through. The engine vouches for an image's magic numbers and no
@@ -18,7 +181,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
181
  that asked for the bytes and keeps the failing class and message behind it,
19
182
  so one report reads the same however it is rendered. It is not a diagnostic:
20
183
  `isDiagnostic` does not answer for one.
21
-
22
184
  - **Formatted cells render dramatically faster.** The engine built a fresh
23
185
  `Intl` formatter for every presented cell and now keeps them, keyed on
24
186
  everything a formatter is built from — the kind, the locale, the digit count,
@@ -74,14 +236,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
74
236
 
75
237
  - **`sort` is stable**: rows whose keys compare equal keep the order they
76
238
  arrived in. The engine has always sorted this way; the promise is new.
77
-
78
239
  - `currencyOf(style, options)` joins the package's exports beside `format()`.
79
240
  It answers which currency code a cell wears — its own when it declares one,
80
241
  else the instance default — for a target that needs the code itself rather
81
242
  than presented text. A cell that declared a code the engine could not accept
82
243
  carries `style.currency` as `null`, so `style.currency ?? yourDefault` is the
83
244
  wrong spelling and this helper is the right one.
84
-
85
245
  - **A cell may name its own currency.** `currency` is a new style declaration
86
246
  beside `format` — a three-letter ISO 4217 code saying what a money cell is
87
247
  denominated in.
@@ -134,14 +294,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
134
294
  - `split-start` events carry `path`, the split definition's schema path, as
135
295
  `item` and `image` events already do. A consumer can now name the definition
136
296
  behind a split without tracking position.
137
-
138
297
  - `fractionDigits(kind, options)` is exported beside `format()`: how many
139
298
  fraction digits a kind presents, or nothing for a kind that carries no
140
299
  count. It is the table the official targets read, and it is public so a
141
300
  consumer writing its own target presents the same digits they do.
142
-
143
- ### Changed
144
-
145
301
  - **A `date` cell with no `form` now presents `medium`, not the runtime's own
146
302
  default.** A `date` that names no form used to render whatever the platform's
147
303
  default date format was — under `en-US`, `8/14/2026` — which matched none of
@@ -162,18 +318,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
162
318
  to write no number format. Read `style.format.kind` where you read
163
319
  `style.format`, and take the digit count off the declaration rather than
164
320
  computing one.
165
-
166
321
  - **`fractionDigits` takes the whole style.** Its first argument was the kind
167
322
  and is now the style, matching `format()` and `currencyOf()`.
168
323
  `fractionDigits("number")` becomes
169
324
  `fractionDigits({ format: "number" })`. Targets no longer need it at all —
170
325
  the count rides on the declaration the stream carries.
171
-
172
326
  - **`typed()`'s second argument is the resolved declaration.** Pass
173
327
  `style.format` as it arrives on the stream; it reads the kind from the
174
328
  object. The shorthand string is still accepted, and omitting the argument is
175
329
  unchanged.
176
-
177
330
  - **A rejected box value is now dropped from the resolved style rather than
178
331
  carried on it.** A `border*` or `padding*` declaration written as an `=`
179
332
  expression used to cross the render stream with whatever the expression
@@ -312,23 +465,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
312
465
  - A `currency` code that is not a readable currency now presents nothing in
313
466
  every target, rather than the worksheet inventing a number format from it
314
467
  while the other targets fell back to the unformatted value.
315
-
316
- ### Fixed
317
-
318
468
  - **A split slot is typed with the declarations the traversal accepts.**
319
469
  `SplitSlot` reused the band items' style types, so TypeScript refused
320
470
  `valign` on an image slot — legal there, and only there — and admitted
321
471
  `spaceBefore`/`spaceAfter` on a slot, which the engine rejects. Two new
322
472
  exported types, `SlotStyleDeclarations` and `SlotImageStyleDeclarations`,
323
473
  now say what a slot may declare.
324
-
325
474
  - **A page band closure is typed as what it returns.** `PageBandRenderers`
326
475
  declared `header`/`footer` as returning item and image events only, while a
327
476
  split in a page band has always come back as its bracket — `split-start`,
328
477
  one event per slot, `split-end`. The closures now return `PageBandEvent[]`,
329
478
  a new exported union naming all four, so a TypeScript host reading a page
330
479
  band no longer types `role` onto a `split-end` that carries none.
331
-
332
480
  - **A computed style value the schema does not accept now leaves the layer
333
481
  below in place**, which is what the schema has always said it does —
334
482
  "contributes nothing, same as omit". The name is dropped from the resolved
@@ -363,7 +511,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
363
511
  the cell falls back to display text, as the documented rule says any
364
512
  unrecognised kind does. Before, such a cell could render `[object Undefined]`.
365
513
  A literal was never affected: it is checked against the four kinds.
366
-
367
514
  - **A pinned report header now refuses a lead on every band that can sit under
368
515
  its box, and only where the document settles which one that is.** When
369
516
  `header` declares a `height`, an authored `spaceBefore` on the first item of
@@ -393,9 +540,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
393
540
  than reporting against the report header, which is a band this rule never
394
541
  covers.
395
542
 
396
- ## [0.5.0] - 2026-09-05
543
+ ## 0.5.0
397
544
 
398
- ### Added
545
+ ### Minor Changes
399
546
 
400
547
  - **`STYLE_NAMES`**, every name in the style vocabulary as a read-only list,
401
548
  in the order the specification's table lists them. A tool that offers the
@@ -417,9 +564,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
417
564
  on a band image, whose boxes have no such slack. A row's or a split's layers
418
565
  under its cells' or slots' own. Undeclared is not a declaration: each target
419
566
  keeps its own default.
420
-
421
- ### Changed
422
-
423
567
  - **A table row's `style` now resolves onto that row's cells**, the box
424
568
  included, instead of meaning something different on every output. Before,
425
569
  padding and border on `detail.header`, `detail.row` or a total row's `style`
@@ -442,9 +586,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
442
586
  carries only what layers by ordinary means, and a row whose whole block was
443
587
  box carries no `style` at all.
444
588
 
445
- ## [0.4.0] - 2026-09-03
589
+ ## 0.4.0
446
590
 
447
- ### Changed
591
+ ### Minor Changes
448
592
 
449
593
  - **`format: "date"` now reads a date string, not only a `Date`.** A JSON
450
594
  document has no date type, so the kind could not be reached from parsed
@@ -468,9 +612,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
468
612
  - **`typed(tokens, kind?)` takes the cell's `format` kind.** Passing `"date"`
469
613
  opts into the same revival, for consumers that write typed cells. The
470
614
  one-argument call is unchanged.
471
-
472
- ### Fixed
473
-
474
615
  - **The TypeScript declarations accept the box.** The per-side `padding*` and
475
616
  `border*` names, and the table's `detail.header`, were validated by the
476
617
  engine from 0.3.0 but were missing from the shipped declarations, so a
@@ -485,9 +626,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
485
626
  about rendering changes: a report that compiled through a cast produces the
486
627
  same output without one.
487
628
 
488
- ## [0.3.0] - 2026-09-02
629
+ ## 0.3.0
489
630
 
490
- ### Added
631
+ ### Minor Changes
491
632
 
492
633
  - **`format` is a closed style name.** `number` / `currency` / `percent` /
493
634
  `date` on text items, column cells, headers, and totals. Not images, not
@@ -495,19 +636,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
495
636
  live on `quario({ locale, currency, timeZone })`. The public `format()`
496
637
  helper is how targets present a kind; a kind on the wrong type contributes
497
638
  nothing.
498
-
499
639
  - **The report header may pin a `height` from the page top.** Dual-shaped
500
640
  like `detail`: an item array, or `{ height, items }`. `page.margin` is a
501
641
  document field (one number, all four sides), required with `height` and
502
642
  legal without. Authored `spaceBefore` on the first occupying item of the
503
643
  next band is refused.
504
-
505
644
  - **`spaceBefore` / `spaceAfter` return as item flow spacing.** Blank space
506
645
  before or after a band item, in points, including band images and splits as
507
646
  band items. Adjacent gaps add. Table cells, `row.style`, headers, totals,
508
647
  and split slots refuse the names. `spaceBefore` drops at a fresh body page
509
648
  or strip top; page-band items keep it. Leading and inset stay cut.
510
-
511
649
  - **Per-side padding and border on the closed style vocabulary.**
512
650
  `paddingTop` / `Right` / `Bottom` / `Left` (points, ≥ 0) and, per side,
513
651
  `border*Width`, `border*Style` (`solid` | `dashed` | `dotted`),
@@ -517,18 +655,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
517
655
  than a solid black stroke. The names are legal wherever `background` is,
518
656
  including images, plus `row.style`. The report default still takes only
519
657
  `family` and `size`. Column `%` widths are border-box.
520
-
521
658
  - **`detail.header` is the header-row box.** `{ style }` only, a different
522
659
  path from `columns[i].header`. It crosses the seam as `table-start.style`.
523
-
524
- ### Changed
525
-
526
660
  - **A table total is N rows.** Before, `total` was one row: a flat cell
527
661
  array or `{ style, cells }`. After, it is absent or a non-empty array of
528
662
  `{ cells, style?, visible? }`. A one-row total is `[{ cells: [...] }]`.
529
663
  `total: []` is a definition error. Paths are `detail.total[r].cells[i]`.
530
664
  The stream yields one `total-row` per emitted row.
531
-
532
665
  - **A visible text item occupies a line at its own `size`.** Empty display
533
666
  used to take the report default's leading in PDF and collapse in HTML;
534
667
  `"a\n\nb"` broke in PDF and collapsed to a space in HTML. A visible item
@@ -537,9 +670,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
537
670
  table cells stay contentless for height. This is not a spacing primitive;
538
671
  `visible: false` is still how an item leaves the layout.
539
672
 
540
- ## [0.2.0] - 2026-09-01
673
+ ## 0.2.0
541
674
 
542
- ### Added
675
+ ### Minor Changes
543
676
 
544
677
  - **A report default: one `style` block for the whole document.** A top-level
545
678
  `style` beside `header`/`detail`/`footer` states the typeface a report is set
@@ -549,21 +682,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
549
682
  a text declaration on an image item is. Values are literals or `=`
550
683
  expressions like any other style block's, resolved once per render in report
551
684
  scope.
552
-
553
685
  - **`report-start` carries the resolved report default as `style`.** A
554
686
  report-level fact, never merged into an item's own `style`: an event's
555
687
  `style` stays what the author wrote on that node, and a consumer composes the
556
688
  default itself, once — under its own band-role defaults and under every
557
689
  event's own style. Absent when the report declares none, so a consumer
558
690
  written before this field renders in its own baseline exactly as it did.
559
-
560
691
  - **An `uppercase` style declaration.** A boolean beside `bold` and `italic`,
561
692
  literal or an `=` expression, for the capitalised column labels business
562
693
  forms are usually set with. It is capitals, not small caps — real small caps
563
694
  need a font feature the PDF target's built-in faces cannot supply, so the
564
695
  declaration promises only what every target can draw. Image items keep
565
696
  refusing it, as they refuse every text declaration.
566
-
567
697
  - **A `split` item places values across the line instead of down the band.**
568
698
  The invoice header's "seller left, customer right", which a band could not
569
699
  say before. `{ "type": "split", "slots": [...] }` takes two or more slots,
@@ -574,13 +704,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
574
704
  a slot that renders nothing keeps its width, so a line's geometry never
575
705
  moves with the data. Splits may appear in every item array except table and
576
706
  total cells.
577
-
578
707
  - **`split-start` / `split-end` bracket a split's slots on the event stream.**
579
708
  `split-start` carries the slot geometry, then one ordinary `item` or `image`
580
709
  event per slot in order, then `split-end`. Existing consumers need no
581
710
  change: the walk driver's missing-handler rule means a target that ignores
582
711
  the bracket still receives the slot items and renders them stacked.
583
-
584
712
  - **`quario().plan(schema, funcs?)` hands the whole traversal over at once.**
585
713
  Returns `{ report, problems, anchors }` from one descent: the compiled
586
714
  report (`null` while the document has problems), every problem as
@@ -604,12 +732,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
604
732
  display rule behind the token join, re-exported beside `text()` so a stream
605
733
  consumer that stringifies token values itself renders exactly what the
606
734
  official targets render — Dates included.
607
-
608
735
  - **`maxDepth: Infinity` opts a query budget out.** The data query's traversal
609
736
  budgets accept an explicit `Infinity` per key for "this budget, unbounded".
610
737
  The 500-deep default is unchanged — it is now padvinder's own, applied for
611
738
  every consumer rather than added by quario at the seam.
612
-
613
739
  - **Located data-query errors carry padvinder's code and span.** A `data`
614
740
  query that does not parse now surfaces with padvinder's `code` and
615
741
  `start`/`end` offsets into the query you wrote — filter faults included —
@@ -619,23 +745,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
619
745
  `PADVINDER_UNKNOWN_FUNCTION`, or `PADVINDER_SYNTAX` for a path character or
620
746
  filter body that is open-endedly not a query. Traversal budgets exceeded at
621
747
  render time keep `limit`/`actual` and carry no span.
622
-
623
- ### Changed
624
-
625
748
  - **Cells render straight over the engine scope chain.** A text cell no longer
626
749
  allocates a wrapper scope and an anchor pair per cell per row — quario's
627
750
  chain already binds `$` at the render base and `@` on the detail row, and
628
751
  sjabloon now renders over it as-is. A 4-column stream over a million rows
629
752
  went from 1.7s to 0.5s. No report changes what it renders. Requires
630
753
  sjabloon 0.11.
631
-
632
754
  - **A compiled report's `functions` carry signatures.** Each entry is now
633
755
  `{ name, arity, doc? }` instead of a bare name — `arity` from the function's
634
756
  declared parameter count (or its own numeric `arity` where rest parameters
635
757
  mislead `length`), `doc` from an own `doc` string when it carries one — in
636
758
  the same call-first-seen order. `names` is unchanged. Requires xprsn 0.11
637
759
  and sjabloon 0.11.
638
-
639
760
  - **A bare `Date` renders as ISO 8601 UTC, the same on every machine.**
640
761
  Display text for a `Date` value was `String(date)`, which bakes the
641
762
  rendering host's timezone and locale into the output — so one report
@@ -644,7 +765,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
644
765
  rule; an invalid `Date` keeps its deterministic `Invalid Date` text.
645
766
  Reports that want a formatted date keep using a registered function,
646
767
  exactly as before.
647
-
648
768
  - **A bad literal pattern in the data query is a definition error.** A typo'd
649
769
  I-Regexp written as a string literal in `match()`/`search()` used to
650
770
  produce a plausible empty report with no signal; `report()` and
@@ -656,7 +776,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
656
776
  fault in an expression: the engine that decided the fault names it. A
657
777
  pattern that arrives from render data keeps RFC 9535 semantics and still
658
778
  matches nothing at render time. Requires padvinder 0.8.
659
-
660
779
  - **Each engine relocates its own diagnostic.** A located error is now a copy
661
780
  made by the engine that raised it (xprsn, sjabloon, or padvinder), so it
662
781
  carries every field that engine puts on a diagnostic — nothing is lost in
@@ -664,9 +783,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
664
783
  passes that engine's own `isDiagnostic`. Host errors are wrapped as plain
665
784
  errors with no diagnostic metadata, exactly as before. Requires xprsn 0.10,
666
785
  sjabloon 0.9, and padvinder 0.5. No report changes what it renders.
667
-
668
- ### Fixed
669
-
670
786
  - **A host error cannot pose as the report's diagnostic.** `report()` rethrows
671
787
  the first definition problem's located engine error; it chose that error by
672
788
  probing for a `code` property, so a host error class that stamps `code` on
@@ -675,9 +791,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
675
791
  `isDiagnostic`; everything else falls back to a plain `SyntaxError` naming
676
792
  the first problem, as before.
677
793
 
678
- ## [0.1.0] - 2026-08-27
794
+ ## 0.1.0
679
795
 
680
- ### Added
796
+ ### Minor Changes
681
797
 
682
798
  - **A compile-once report engine.** You hand it a JSON report definition and
683
799
  data; it plans once and streams render events that every target consumes.
package/README.md CHANGED
@@ -98,19 +98,28 @@ row, one `total-row` per emitted total row, and `table-end`.
98
98
  | `table-end` | - |
99
99
  | `report-end` | - |
100
100
 
101
+ The full field semantics are in the [event stream reference](https://getquario.com/docs/reference/events/).
102
+
101
103
  **Cells carry tokens.** A cell is `{ tokens, style? }`. Each token is either `{ literal }`
102
104
  (static template text, verbatim) or `{ value }` (one interpolation's _pre-format_ value, the
103
105
  expression result before any stringification). This is the typed seam: a cell whose template is `{{ @.amount }}` holds one value token with the
104
106
  number itself, so a spreadsheet consumer writes a real numeric cell. `Total: {{ @.amount }}` mixes
105
107
  a literal and a value, so the cell is text only.
106
108
 
109
+ **A page-number token names itself.** A value token whose interpolation is exactly
110
+ `{{ page.number }}` or `{{ page.total }}` also carries `field`, holding that same string. The
111
+ value is the number this page was rendered with; `field` says which page value the token stands
112
+ for, so a target whose own document format numbers pages can write its own live field there
113
+ instead. Anything computed from them carries no `field`.
114
+
107
115
  **Escaping is the consumer's job.** A target that embeds values in markup must escape them at its
108
116
  own edge.
109
117
 
110
118
  **`report-start.marking`** carries the evaluation wording when the render is unlicensed, or while
111
119
  verification is still settling. Licensed streams omit it. Targets place the marking; they do not
112
120
  author its wording. **`columns`** on `report-start` / `group-start` is the declared
113
- page column count when present. `@quario/pdf` and `@quario/html` lay page columns out;
121
+ [page column](https://getquario.com/docs/diving-deeper/bands/#flowing-in-columns) count when
122
+ present. `@quario/pdf` and `@quario/html` lay page columns out;
114
123
  xlsx never will.
115
124
 
116
125
  ### `text(tokens)`
@@ -127,6 +136,14 @@ display text. Passing the cell's resolved `format` kind opts into the seam's one
127
136
  real numeric cells;
128
137
  [`@quario/csv`](https://www.npmjs.com/package/@quario/csv) is the short form.
129
138
 
139
+ ### `styledRuns(tokens)`
140
+
141
+ Groups a cell's tokens into its **styled runs**, in order — each `{ style, tokens }`,
142
+ with `style` `null` where the tokens carry none and the cell's own applies. Consecutive tokens
143
+ with equal styles are one run, which is lossless: equal styles render identically, so the grouping
144
+ survives a JSON round trip. Every built-in target reads a cell's runs through this, so a custom
145
+ target cannot drift from them.
146
+
130
147
  ### `walk(events, handlers)` / `breathe()`
131
148
 
132
149
  `walk` is the delivery driver every official target uses. Pass one render's event iterable and
@@ -154,7 +171,8 @@ cell the same way:
154
171
  - `currencyOf(style?, options?)` — which code a money cell wears: its own, else the instance's.
155
172
  - `isReportBand(role)` — whether a role names one of the report's own bands rather than a
156
173
  group's.
157
- - `STYLE_NAMES` — the closed style vocabulary, in the spec's order.
174
+ - `STYLE_NAMES` — the closed style vocabulary, in the spec's order, and
175
+ `RUN_STYLE_NAMES` — the inline half of it, which is what a styled run may wear.
158
176
 
159
177
  ### `validate(schema, functions?)`
160
178
 
@@ -172,34 +190,49 @@ so `validate()` can never disagree with what `report()` accepts.
172
190
  ### `quario().plan(schema, functions?)`
173
191
 
174
192
  The one traversal, whole — for hosts that validate and render in a loop, like an editor. Returns
175
- `{ report, problems, anchors }`: the compiled report (`null` while the document has problems),
176
- every problem structurally as `{ path, source?, message, diagnostic? }` (the `message` is exactly
177
- `validate()`'s string, and every problem keeps its own located diagnostic with `start`/`end`
178
- offsets, not only the first), and `anchors`, mapping each compiled source's schema path to the
179
- anchors and group handles it reads — the unfiltered complement of `names`.
193
+ `{ report, problems, anchors, warnings }`: the compiled report (`null` while the document has
194
+ problems), every problem structurally as `{ path, source?, message, diagnostic? }` (the `message`
195
+ is exactly `validate()`'s string, and every problem keeps its own located diagnostic with
196
+ `start`/`end` offsets, not only the first), `anchors`, mapping each compiled source's schema path
197
+ to the anchors and group handles it reads — the unfiltered complement of `names` — and
198
+ `warnings`.
199
+
200
+ A warning is `{ path, source?, message }`: the document declares something nothing will read. It
201
+ is not a problem at a lower severity, which is why it has no `diagnostic` — nothing raised, the
202
+ engine decided. Neither warning below locates into an authored source, so none carries a `source`
203
+ today. **A warning is never fatal**: a document carrying only warnings compiles and
204
+ renders, so `report` is `null` on `problems` alone. Two declarations warn today — a `currency` on
205
+ a cell whose `format` is not `"currency"`, and a table where every column is sized and the widths
206
+ total under 100 — and the list is advisory and deliberately incomplete, so a quiet one is not a
207
+ promise that every declaration will be read. `validate()` returns problems only.
180
208
 
181
209
  ```js
182
- const { report, problems, anchors } = quario().plan(schema);
210
+ const { report, problems, anchors, warnings } = quario().plan(schema);
211
+ for (const warning of warnings) console.warn(warning.message);
183
212
  if (report) await report.render(html(), data);
184
213
  else console.error(problems[0].path, problems[0].message);
185
214
  ```
186
215
 
187
216
  ### `isDiagnostic(error)`
188
217
 
189
- True when a caught value is one of the stack's located errors: quario's own, or one thrown
190
- directly by xprsn, sjabloon, or padvinder. Authentication checks identity. An error that only
191
- matches the shape does not pass.
218
+ True when a caught value is a **located diagnostic**: an error xprsn, sjabloon or padvinder
219
+ minted — thrown by that engine, or re-thrown by quario with the engine original behind it.
220
+ Authentication checks identity. An error that only matches the shape does not pass.
192
221
 
193
- Located errors name their band path and offending source while keeping their original type
194
- (`SyntaxError`, `TypeError`, `RangeError`). They carry `code`, `start`/`end` offsets, and (for
195
- query budget failures) `limit` and `actual`.
222
+ Being located is not what the guard reads: quario's own verdicts on a document and a registered
223
+ function's own throw are located too, and neither is a diagnostic. Errors a report throws
224
+ name the path they failed at — and the offending source, where there is one — while keeping their
225
+ original type (`SyntaxError`, `TypeError`, `RangeError`). What a diagnostic adds on top is
226
+ metadata an engine vouches for: `code`, `start`/`end` offsets, and, for a query budget in place
227
+ of those offsets, `limit` and `actual`.
196
228
 
197
229
  ```js
198
230
  try {
199
231
  await report.render(html(), data);
200
232
  } catch (e) {
201
- if (isDiagnostic(e)) console.error("report problem:", e.message);
202
- else throw e; // one of your own functions failed
233
+ // Every error names where it failed; a diagnostic also carries an engine's own metadata.
234
+ if (isDiagnostic(e)) console.error(e.code, e.start, e.end);
235
+ throw e;
203
236
  }
204
237
  ```
205
238
 
@@ -232,7 +265,8 @@ Markdown target lives in the repository at `example/markdown.js` in about 70 lin
232
265
  targets are written against the same public API.
233
266
 
234
267
  Two rules a target owes its users: escape or neutralize every `value` token at your own edge, and
235
- map the style vocabulary to your own formatting model rather than expecting CSS.
268
+ map the [style vocabulary](https://getquario.com/docs/reference/style-declarations/) to your own
269
+ formatting model rather than expecting CSS.
236
270
 
237
271
  The stream is additive. The walk driver skips any event you register no handler for, so a target
238
272
  that ignores a newer event (for example, `example/markdown.js` has none for `image`) keeps rendering
@@ -248,6 +282,13 @@ package, so it runs under a script policy that omits `unsafe-eval`. The test sui
248
282
  under Node's `--disallow-code-generation-from-strings` flag, a source scan, and a Playwright
249
283
  harness that loads the published files under a strict CSP.
250
284
 
285
+ ## Documentation
286
+
287
+ [The quario documentation](https://getquario.com/docs/) is the reference.
288
+ The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
289
+ specification of what a report may declare, and
290
+ the [engine reference](https://getquario.com/docs/reference/quario/) is this package's own API.
291
+
251
292
  ## License
252
293
 
253
294
  Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer