quario 0.1.0 → 0.2.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
@@ -7,6 +7,144 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-09-01
11
+
12
+ ### Added
13
+
14
+ - **A report default: one `style` block for the whole document.** A top-level
15
+ `style` beside `header`/`detail`/`footer` states the typeface a report is set
16
+ in, so a document with one face names it once instead of on every item, every
17
+ table column, every header and every total cell. It takes `family` and
18
+ `size`; any other declaration there is a located definition error, exactly as
19
+ a text declaration on an image item is. Values are literals or `=`
20
+ expressions like any other style block's, resolved once per render in report
21
+ scope.
22
+
23
+ - **`report-start` carries the resolved report default as `style`.** A
24
+ report-level fact, never merged into an item's own `style`: an event's
25
+ `style` stays what the author wrote on that node, and a consumer composes the
26
+ default itself, once — under its own band-role defaults and under every
27
+ event's own style. Absent when the report declares none, so a consumer
28
+ written before this field renders in its own baseline exactly as it did.
29
+
30
+ - **An `uppercase` style declaration.** A boolean beside `bold` and `italic`,
31
+ literal or an `=` expression, for the capitalised column labels business
32
+ forms are usually set with. It is capitals, not small caps — real small caps
33
+ need a font feature the PDF target's built-in faces cannot supply, so the
34
+ declaration promises only what every target can draw. Image items keep
35
+ refusing it, as they refuse every text declaration.
36
+
37
+ - **A `split` item places values across the line instead of down the band.**
38
+ The invoice header's "seller left, customer right", which a band could not
39
+ say before. `{ "type": "split", "slots": [...] }` takes two or more slots,
40
+ each an ordinary `text` or `image` item plus an optional `width` percentage
41
+ under the same rules a table column's obeys; width-less slots divide what
42
+ the sized ones leave, evenly. A split is always the full content width and
43
+ never nests — it says what sits beside what, never where anything is — and
44
+ a slot that renders nothing keeps its width, so a line's geometry never
45
+ moves with the data. Splits may appear in every item array except table and
46
+ total cells.
47
+
48
+ - **`split-start` / `split-end` bracket a split's slots on the event stream.**
49
+ `split-start` carries the slot geometry, then one ordinary `item` or `image`
50
+ event per slot in order, then `split-end`. Existing consumers need no
51
+ change: the walk driver's missing-handler rule means a target that ignores
52
+ the bracket still receives the slot items and renders them stacked.
53
+
54
+ - **`quario().plan(schema, funcs?)` hands the whole traversal over at once.**
55
+ Returns `{ report, problems, anchors }` from one descent: the compiled
56
+ report (`null` while the document has problems), every problem as
57
+ `{ path, source?, message, diagnostic? }`, and a map from each compiled
58
+ source's schema path to the anchors and group handles it reads. A host that
59
+ validates and renders per edit — an editor — pays one traversal instead of
60
+ two, can point at a problem in its own UI without parsing a message apart,
61
+ and can tell where a node may safely move. `validate()` is unchanged: its
62
+ strings are those problems' `message` fields.
63
+ - **Every problem keeps its own located diagnostic.** `validate()` retained
64
+ `start`/`end` offsets on at most one problem per document; the structured
65
+ list carries each problem's own, so a consumer can underline the offending
66
+ character in every faulty expression rather than only the first.
67
+ - **Events name the definition behind them.** `item`, `image` and
68
+ `group-start` events carry `path`, the schema path of the node that
69
+ produced them; `table-start` carries `detail` and its columns their own,
70
+ and every table cell carries its column definition's path (a total cell its
71
+ `detail.total[i]` entry). Rows carry no identity — a row is data, not
72
+ schema. Additive: a consumer that ignores the field is unaffected.
73
+ - **`display(value)` joins one value the way `text()` does.** The scalar
74
+ display rule behind the token join, re-exported beside `text()` so a stream
75
+ consumer that stringifies token values itself renders exactly what the
76
+ official targets render — Dates included.
77
+
78
+ - **`maxDepth: Infinity` opts a query budget out.** The data query's traversal
79
+ budgets accept an explicit `Infinity` per key for "this budget, unbounded".
80
+ The 500-deep default is unchanged — it is now padvinder's own, applied for
81
+ every consumer rather than added by quario at the seam.
82
+
83
+ - **Located data-query errors carry padvinder's code and span.** A `data`
84
+ query that does not parse now surfaces with padvinder's `code` and
85
+ `start`/`end` offsets into the query you wrote — filter faults included —
86
+ alongside the band/item path quario already attached. The code names the
87
+ category of mistake: `PADVINDER_MISSING_ROOT`, `PADVINDER_BAD_SELECTOR`,
88
+ `PADVINDER_UNCLOSED_BRACKET`, `PADVINDER_BAD_STRING`,
89
+ `PADVINDER_UNKNOWN_FUNCTION`, or `PADVINDER_SYNTAX` for a path character or
90
+ filter body that is open-endedly not a query. Traversal budgets exceeded at
91
+ render time keep `limit`/`actual` and carry no span.
92
+
93
+ ### Changed
94
+
95
+ - **Cells render straight over the engine scope chain.** A text cell no longer
96
+ allocates a wrapper scope and an anchor pair per cell per row — quario's
97
+ chain already binds `$` at the render base and `@` on the detail row, and
98
+ sjabloon now renders over it as-is. A 4-column stream over a million rows
99
+ went from 1.7s to 0.5s. No report changes what it renders. Requires
100
+ sjabloon 0.11.
101
+
102
+ - **A compiled report's `functions` carry signatures.** Each entry is now
103
+ `{ name, arity, doc? }` instead of a bare name — `arity` from the function's
104
+ declared parameter count (or its own numeric `arity` where rest parameters
105
+ mislead `length`), `doc` from an own `doc` string when it carries one — in
106
+ the same call-first-seen order. `names` is unchanged. Requires xprsn 0.11
107
+ and sjabloon 0.11.
108
+
109
+ - **A bare `Date` renders as ISO 8601 UTC, the same on every machine.**
110
+ Display text for a `Date` value was `String(date)`, which bakes the
111
+ rendering host's timezone and locale into the output — so one report
112
+ produced different bytes on different machines. Every target now renders a
113
+ valid `Date` as its `toISOString()` form through the one shared display
114
+ rule; an invalid `Date` keeps its deterministic `Invalid Date` text.
115
+ Reports that want a formatted date keep using a registered function,
116
+ exactly as before.
117
+
118
+ - **A bad literal pattern in the data query is a definition error.** A typo'd
119
+ I-Regexp written as a string literal in `match()`/`search()` used to
120
+ produce a plausible empty report with no signal; `report()` and
121
+ `validate()` now surface it at compile time as a located error, with
122
+ offsets at the pattern literal in the query. The class and `code` are
123
+ treffer's — a malformed pattern is a `SyntaxError` with `TREFFER_SYNTAX`,
124
+ one past treffer's limits a `RangeError` with a `TREFFER_MAX_*` code and
125
+ `limit`/`actual` — on the same rule that already gives xprsn's codes to a
126
+ fault in an expression: the engine that decided the fault names it. A
127
+ pattern that arrives from render data keeps RFC 9535 semantics and still
128
+ matches nothing at render time. Requires padvinder 0.8.
129
+
130
+ - **Each engine relocates its own diagnostic.** A located error is now a copy
131
+ made by the engine that raised it (xprsn, sjabloon, or padvinder), so it
132
+ carries every field that engine puts on a diagnostic — nothing is lost in
133
+ quario's re-throw, today or when an engine adds a field — and it still
134
+ passes that engine's own `isDiagnostic`. Host errors are wrapped as plain
135
+ errors with no diagnostic metadata, exactly as before. Requires xprsn 0.10,
136
+ sjabloon 0.9, and padvinder 0.5. No report changes what it renders.
137
+
138
+ ### Fixed
139
+
140
+ - **A host error cannot pose as the report's diagnostic.** `report()` rethrows
141
+ the first definition problem's located engine error; it chose that error by
142
+ probing for a `code` property, so a host error class that stamps `code` on
143
+ itself in its constructor was rethrown as if the engines had raised it. The
144
+ choice now goes through the same identity-based authentication as
145
+ `isDiagnostic`; everything else falls back to a plain `SyntaxError` naming
146
+ the first problem, as before.
147
+
10
148
  ## [0.1.0] - 2026-08-27
11
149
 
12
150
  ### Added
package/README.md CHANGED
@@ -84,11 +84,11 @@ row, an optional `total-row`, and `table-end`.
84
84
  | Event | Carries |
85
85
  | -------------- | ------------------------------------------------------------------------------------------- |
86
86
  | `report-start` | `params`, resolved report `aggregates`, optional `page` band closures, `columns`, `marking` |
87
- | `item` | `role`, `tokens`, optional `style`, `run` |
88
- | `image` | `role`, `bytes`, `format` (`png` \| `jpeg`), `fit`, optional `alt`, `style`, `run` |
89
- | `group-start` | `name`, `depth`, `key`, `aggregates`, optional `break`, `reset`, `columns` |
87
+ | `item` | `role`, `path`, `tokens`, optional `style`, `run` |
88
+ | `image` | `role`, `path`, `bytes`, `format` (`png` \| `jpeg`), `fit`, optional `alt`, `style`, `run` |
89
+ | `group-start` | `name`, `depth`, `key`, `aggregates`, `path`, optional `break`, `reset`, `columns` |
90
90
  | `group-end` | `name`, `depth` |
91
- | `table-start` | `columns` (each `header`, optional `width`) |
91
+ | `table-start` | `path`, `columns` (each `header`, `path`, optional `width`) |
92
92
  | `row` | `cells`, optional `style`, `run` |
93
93
  | `total-row` | `cells` |
94
94
  | `table-end` | - |
@@ -145,6 +145,21 @@ validate({ data: "$.o[*]", sort: [{ by: "=@.a", dir: "up" }] });
145
145
  `report()` throws on the first problem instead. Validation and compilation share one traversal,
146
146
  so `validate()` can never disagree with what `report()` accepts.
147
147
 
148
+ ### `quario().plan(schema, functions?)`
149
+
150
+ The one traversal, whole — for hosts that validate and render in a loop, like an editor. Returns
151
+ `{ report, problems, anchors }`: the compiled report (`null` while the document has problems),
152
+ every problem structurally as `{ path, source?, message, diagnostic? }` (the `message` is exactly
153
+ `validate()`'s string, and every problem keeps its own located diagnostic with `start`/`end`
154
+ offsets, not only the first), and `anchors`, mapping each compiled source's schema path to the
155
+ anchors and group handles it reads — the unfiltered complement of `names`.
156
+
157
+ ```js
158
+ const { report, problems, anchors } = quario().plan(schema);
159
+ if (report) await report.render(html(), data);
160
+ else console.error(problems[0].path, problems[0].message);
161
+ ```
162
+
148
163
  ### `isDiagnostic(error)`
149
164
 
150
165
  True when a caught value is one of the stack's located errors: quario's own, or one thrown
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { PadvinderErrorCode, QueryOptions, QueryPath } from "padvinder";
1
+ import type { PadvinderErrorCode, QueryOptions, QueryPath, TrefferErrorCode } from "padvinder";
2
+ import type { XprsnErrorCode, XprsnSignature } from "xprsn";
2
3
  import type {
3
4
  LiteralToken as SjabloonLiteralToken,
4
5
  SjabloonBlock,
@@ -44,6 +45,8 @@ export interface StyleDeclarations {
44
45
  italic?: ExpressionValue<boolean>;
45
46
  underline?: ExpressionValue<boolean>;
46
47
  strikethrough?: ExpressionValue<boolean>;
48
+ /** Render the text in capitals. Capitals, not small caps. */
49
+ uppercase?: ExpressionValue<boolean>;
47
50
  /** Text color, `#rgb`/`#rrggbb`. */
48
51
  color?: ExpressionValue<string>;
49
52
  /** Background color, `#rgb`/`#rrggbb`. */
@@ -84,6 +87,12 @@ export type ImageFit = "natural" | "width";
84
87
  */
85
88
  export type ImageStyleDeclarations = Pick<StyleDeclarations, "background" | "align">;
86
89
 
90
+ /**
91
+ * What a report default declares: the two declarations a typeface is made of.
92
+ * Anything else is a definition error, as on an image item.
93
+ */
94
+ export type ReportStyleDeclarations = Pick<StyleDeclarations, "family" | "size">;
95
+
87
96
  /**
88
97
  * A host-supplied raster graphic in the band flow. `source` is an expression
89
98
  * (never a literal — JSON has no way to write bytes) yielding the complete
@@ -101,8 +110,31 @@ export interface ImageItem {
101
110
  style?: ImageStyleDeclarations;
102
111
  }
103
112
 
104
- /** One item in a band: text, or an image. Future types may be additive. */
105
- export type Item = TextItem | ImageItem;
113
+ /**
114
+ * One slot of a split: an ordinary item, plus the width share that is a slot
115
+ * property rather than an item one. A split may not stand in a slot —
116
+ * placement inside placement is the coordinate system quario does not have.
117
+ */
118
+ export type SplitSlot = (TextItem | ImageItem) & {
119
+ /** Slot width as a percentage of the content width (0 < width <= 100). */
120
+ width?: number;
121
+ };
122
+
123
+ /**
124
+ * Items placed across the content width rather than stacked down the band —
125
+ * the invoice header's "seller left, customer right". A split is always the
126
+ * full content width and never nests; a slot that renders nothing keeps its
127
+ * width, so the line's geometry does not move with the data.
128
+ */
129
+ export interface SplitItem {
130
+ type: "split";
131
+ slots: [SplitSlot, SplitSlot, ...SplitSlot[]];
132
+ visible?: ExpressionValue<boolean>;
133
+ style?: StyleDeclarations;
134
+ }
135
+
136
+ /** One item in a band: text, an image, or a split. Future types may be additive. */
137
+ export type Item = TextItem | ImageItem | SplitItem;
106
138
 
107
139
  export interface TableHeader {
108
140
  value: CellValue;
@@ -178,6 +210,12 @@ export interface ReportSchema {
178
210
  aggregates?: Record<string, ReducerSpec>;
179
211
  /** Running accumulators, read as `run.<name>` on detail rows. */
180
212
  run?: Record<string, ReducerSpec>;
213
+ /**
214
+ * The report default: the typeface this document is set in. It sits under
215
+ * the targets' band-role defaults and under every item's own `style`, so a
216
+ * declared `size` does not change a report header's headline size.
217
+ */
218
+ style?: ReportStyleDeclarations;
181
219
  header?: Item[];
182
220
  empty?: Item[];
183
221
  /**
@@ -246,6 +284,12 @@ export type Token = LiteralToken | ValueToken;
246
284
  export interface EventCell {
247
285
  tokens: Token[];
248
286
  style?: Record<string, unknown>;
287
+ /**
288
+ * The definition's schema path, on cells that belong to one: a `row` cell
289
+ * carries its column's (`detail.columns[i]`), a `total-row` cell its
290
+ * `detail.total[i]` entry. Absent on page-band cells reached by closure.
291
+ */
292
+ path?: string;
249
293
  }
250
294
 
251
295
  /** The page anchor a paginated target passes to page band closures. */
@@ -273,6 +317,15 @@ export interface ReportStartEvent {
273
317
  page?: PageBandRenderers;
274
318
  /** The declared page column count, present when the root declares one. */
275
319
  columns?: number;
320
+ /**
321
+ * The resolved report default, present when the report declares one. It is
322
+ * a report-level fact rather than something merged into each item's style,
323
+ * so a consumer composes it once — under its own band-role defaults, and
324
+ * under every event's own `style`. Ignoring it renders the report in the
325
+ * consumer's own baseline, which is what a consumer written before this
326
+ * field already does.
327
+ */
328
+ style?: Record<string, unknown>;
276
329
  /**
277
330
  * The marking wording, present when this render is not covered by a valid
278
331
  * license key — including while verification is still settling (await the
@@ -286,6 +339,8 @@ export interface ReportStartEvent {
286
339
  export interface ItemEvent {
287
340
  type: "item";
288
341
  role: ItemRole;
342
+ /** The item definition's schema path, e.g. `detail[0]`. */
343
+ path: string;
289
344
  tokens: Token[];
290
345
  style?: Record<string, unknown>;
291
346
  run?: Record<string, unknown>;
@@ -297,6 +352,8 @@ export type ImageFormat = "png" | "jpeg";
297
352
  export interface ImageEvent {
298
353
  type: "image";
299
354
  role: ItemRole;
355
+ /** The image definition's schema path. */
356
+ path: string;
300
357
  /** The bytes exactly as the `source` expression yielded them. */
301
358
  bytes: Uint8Array;
302
359
  /** Sniffed from the bytes' magic numbers, so no consumer repeats it. */
@@ -308,9 +365,28 @@ export interface ImageEvent {
308
365
  run?: Record<string, unknown>;
309
366
  }
310
367
 
368
+ /**
369
+ * Opens a split: the slot geometry, then one ordinary `item` or `image` event
370
+ * per slot in order, then `split-end`. A consumer with no handler for the
371
+ * bracket still receives the slot items and renders them stacked.
372
+ */
373
+ export interface SplitStartEvent {
374
+ type: "split-start";
375
+ role: ItemRole;
376
+ /** One entry per slot, in order; `width` is absent on a width-less slot. */
377
+ slots: { width?: number }[];
378
+ style?: Record<string, unknown>;
379
+ }
380
+
381
+ export interface SplitEndEvent {
382
+ type: "split-end";
383
+ }
384
+
311
385
  export interface GroupStartEvent {
312
386
  type: "group-start";
313
387
  name: string;
388
+ /** The group definition's schema path, e.g. `groups[0]`. */
389
+ path: string;
314
390
  depth: number;
315
391
  key: unknown;
316
392
  aggregates: Record<string, unknown>;
@@ -329,7 +405,9 @@ export interface GroupEndEvent {
329
405
 
330
406
  export interface TableStartEvent {
331
407
  type: "table-start";
332
- columns: { header: EventCell; width?: number }[];
408
+ /** Always `detail` the table definition's schema path. */
409
+ path: string;
410
+ columns: { header: EventCell; path: string; width?: number }[];
333
411
  }
334
412
 
335
413
  export interface RowEvent {
@@ -356,6 +434,8 @@ export type ReportEvent =
356
434
  | ReportStartEvent
357
435
  | ItemEvent
358
436
  | ImageEvent
437
+ | SplitStartEvent
438
+ | SplitEndEvent
359
439
  | GroupStartEvent
360
440
  | GroupEndEvent
361
441
  | TableStartEvent
@@ -364,10 +444,19 @@ export type ReportEvent =
364
444
  | TableEndEvent
365
445
  | ReportEndEvent;
366
446
 
447
+ /**
448
+ * One registry function a compiled report calls: its arity (the function's
449
+ * declared parameter count, or its own numeric `arity` where `length`
450
+ * misleads) and its own `doc` string when it carries one — xprsn's
451
+ * `signatures()` convention, in call-first-seen order. Aliased so hosts type
452
+ * the metadata from 'quario' alone, without their own xprsn dependency.
453
+ */
454
+ export type FunctionSignature = XprsnSignature;
455
+
367
456
  export interface ReportEventStream {
368
457
  (data?: unknown): Generator<ReportEvent, void, undefined>;
369
458
  readonly names: readonly string[];
370
- readonly functions: readonly string[];
459
+ readonly functions: readonly FunctionSignature[];
371
460
  readonly paths: readonly QueryPath[];
372
461
  }
373
462
 
@@ -397,10 +486,23 @@ export function walk(events: Iterable<ReportEvent>, handlers: WalkHandlers): Pro
397
486
  */
398
487
  export function breathe(): Promise<void>;
399
488
 
400
- export type QuarioErrorCode = SjabloonErrorCode | PadvinderErrorCode;
489
+ /**
490
+ * Every code a located diagnostic can carry, which is a code from whichever
491
+ * engine decided the fault: quario relocates xprsn's directly, sjabloon
492
+ * carries xprsn's on a fault inside an expression, and padvinder carries
493
+ * treffer's on a pattern literal rejected when the query compiles.
494
+ *
495
+ * `TrefferErrorCode` reaches this union through padvinder, which re-exports
496
+ * it, so quario needs no treffer dependency to name it here.
497
+ */
498
+ export type QuarioErrorCode =
499
+ | SjabloonErrorCode
500
+ | XprsnErrorCode
501
+ | PadvinderErrorCode
502
+ | TrefferErrorCode;
401
503
 
402
504
  export interface QuarioDiagnostic extends Error {
403
- /** Absent on padvinder path-syntax errors; present on every other diagnostic. */
505
+ /** Absent on option and target-definition faults; present on every other diagnostic. */
404
506
  readonly code?: QuarioErrorCode;
405
507
  readonly start?: number;
406
508
  readonly end?: number;
@@ -414,7 +516,7 @@ export interface CompiledReport {
414
516
  /** The raw event seam: one generator of report events per call. */
415
517
  stream(data?: unknown): Generator<ReportEvent, void, undefined>;
416
518
  readonly names: readonly string[];
417
- readonly functions: readonly string[];
519
+ readonly functions: readonly FunctionSignature[];
418
520
  readonly paths: readonly QueryPath[];
419
521
  /**
420
522
  * Render this compiled report through one target. Resolves the target's
@@ -424,6 +526,33 @@ export interface CompiledReport {
424
526
  render<Out>(target: Target<string, Out>, data?: unknown): Promise<Awaited<Out>>;
425
527
  }
426
528
 
529
+ /**
530
+ * One definition problem, structurally: the schema path it sits at, the
531
+ * offending author source when the problem came from compiling one, the whole
532
+ * located message (`validate()`'s string is exactly this field), and the
533
+ * engine's located diagnostic when one authenticated the fault — every
534
+ * problem keeps its own, offsets included, not only the first.
535
+ */
536
+ export interface Problem {
537
+ readonly path: string;
538
+ readonly source?: string;
539
+ readonly message: string;
540
+ readonly diagnostic?: QuarioDiagnostic;
541
+ }
542
+
543
+ /**
544
+ * The plan: both readings of the one traversal, kept. `report` is the
545
+ * compiled report, or null while the document has problems; `problems` is the
546
+ * structured list `validate()` flattens to strings; `anchors` maps each
547
+ * compiled source's schema path to the anchors and group handles it reads —
548
+ * the unfiltered complement of `names`, which excludes them.
549
+ */
550
+ export interface Plan {
551
+ readonly report: CompiledReport | null;
552
+ readonly problems: readonly Problem[];
553
+ readonly anchors: Readonly<Record<string, readonly string[]>>;
554
+ }
555
+
427
556
  export interface Quario {
428
557
  /**
429
558
  * Settles when this instance's license key verification completes;
@@ -432,6 +561,12 @@ export interface Quario {
432
561
  readonly license: Promise<LicenseInfo>;
433
562
  /** Compile a definition once; every target renders from it. */
434
563
  report(schema: ReportSchema, functions?: FunctionRegistry): CompiledReport;
564
+ /**
565
+ * The one traversal, whole: compiled report (null on problems), structured
566
+ * problems, and per-node anchor sets — one descent per edit for a host that
567
+ * validates and renders in a loop.
568
+ */
569
+ plan(schema: unknown, functions?: FunctionRegistry): Plan;
435
570
  }
436
571
 
437
572
  /**
@@ -451,11 +586,20 @@ export function validate(schema: unknown, functions?: FunctionRegistry): string[
451
586
  export function isDiagnostic(e: unknown): e is QuarioDiagnostic;
452
587
 
453
588
  /**
454
- * Join a token stream to display text: literals verbatim, values as
455
- * `String(value ?? '')`. Re-exported from sjabloon.
589
+ * Join a token stream to display text: literals verbatim, values through
590
+ * `display()`. Re-exported from sjabloon.
456
591
  */
457
592
  export function text(tokens: readonly Token[]): string;
458
593
 
594
+ /**
595
+ * Display text for one token value — the scalar rule `text()` joins with,
596
+ * re-exported from sjabloon so targets stringify exactly as the stream does.
597
+ * A valid `Date` renders as ISO 8601 UTC (`toISOString()`), deterministically
598
+ * across machines; an invalid `Date` stays `"Invalid Date"`; nullish displays
599
+ * empty; everything else is `String(value)`.
600
+ */
601
+ export function display(value: unknown): string;
602
+
459
603
  /**
460
604
  * The typed-cell seam: exactly one value token holding a finite number, a
461
605
  * boolean, or a valid Date keeps its pre-stringify value; anything else —