@company-semantics/contracts 35.0.0 → 35.1.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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/src/notifications/README.md +21 -19
  3. package/src/notifications/__tests__/README.md +42 -47
  4. package/src/notifications/__tests__/__snapshots__/README.md +15 -8
  5. package/src/notifications/__tests__/__snapshots__/registry.test.ts.snap +31 -0
  6. package/src/notifications/__tests__/__snapshots__/render-snapshot.test.ts.snap +2 -2
  7. package/src/notifications/__tests__/context.test.ts +5 -5
  8. package/src/notifications/__tests__/definition.test.ts +2 -2
  9. package/src/notifications/__tests__/fixtures.ts +226 -0
  10. package/src/notifications/__tests__/kinds.test.ts +2 -3
  11. package/src/notifications/__tests__/registry.test.ts +27 -27
  12. package/src/notifications/__tests__/render-snapshot.test.ts +10 -6
  13. package/src/notifications/kinds/README.md +2 -2
  14. package/src/notifications/render.ts +6 -6
  15. package/src/notifications/renderers/email/README.md +10 -6
  16. package/src/notifications/renderers/email/__tests__/README.md +19 -16
  17. package/src/notifications/renderers/email/__tests__/render.test.ts +44 -36
  18. package/src/notifications/renderers/email/chat.ts +3 -2
  19. package/src/notifications/renderers/email/constants.ts +17 -0
  20. package/src/notifications/renderers/email/index.ts +4 -4
  21. package/src/notifications/renderers/email/render.ts +15 -2
  22. package/src/notifications/renderers/email/shells.ts +3 -2
  23. package/src/notifications/text.ts +2 -2
  24. package/src/notifications/__tests__/output-parity.golden.ts +0 -363
  25. package/src/notifications/__tests__/output-parity.test.ts +0 -122
@@ -13,11 +13,15 @@
13
13
  * every snapshot key still resolves. That is the whole claim of the move: the
14
14
  * keys still match, so the bytes are still the bytes.
15
15
  *
16
- * Read this alongside `./output-parity.test.ts`, which is the load-bearing one.
17
- * A snapshot can be regenerated with a keystroke; the golden cannot. This file
18
- * exists to make the markup reviewable, not to be the proof.
16
+ * This file used to be the reviewable companion to `./output-parity.test.ts`,
17
+ * which was the load-bearing one: a snapshot can be regenerated with a
18
+ * keystroke, a committed golden cannot, so the golden was the proof and this was
19
+ * the documentation. ADR-CONTRACTS-087 retired that golden once its migration
20
+ * proof shipped in 35.0.0 — so this file is now BOTH. There is no longer a
21
+ * second oracle to catch a snapshot refreshed on autopilot; the diff you read
22
+ * before typing `vitest -u` is the control.
19
23
  *
20
- * The clock is pinned to `OUTPUT_PARITY_CLOCK`'s year rather than left to run.
24
+ * The clock is pinned to `FIXTURE_CLOCK`'s year rather than left to run.
21
25
  * These snapshots used to be captured under the wall clock — with the copyright
22
26
  * year baked into every one of them — so they were due to fail on the next New
23
27
  * Year's Day for calendar reasons alone. The year is a value in `RenderContext`
@@ -32,11 +36,11 @@ import type { NotificationPayloads } from "../payloads";
32
36
  import { renderEmail } from "../render";
33
37
  import { NOTIFICATION_DEFINITIONS } from "../registry";
34
38
  import type { RenderedEmail } from "../renderers/email";
35
- import { OUTPUT_PARITY_CLOCK } from "./output-parity.golden";
39
+ import { FIXTURE_CLOCK } from "./fixtures";
36
40
 
37
41
  /** Pinned so no wall-clock year can reach a snapshot. */
38
42
  const CONTEXT: RenderContextOverrides = {
39
- brand: { copyrightYear: new Date(OUTPUT_PARITY_CLOCK).getUTCFullYear() },
43
+ brand: { copyrightYear: new Date(FIXTURE_CLOCK).getUTCFullYear() },
40
44
  };
41
45
 
42
46
  interface Fixture {
@@ -50,8 +50,8 @@ template literal the compiler checks.
50
50
  - Definitions take `formatExpiry`, `titleCase`, `NOTICE` and `ACCESS_PHRASE` from
51
51
  `../text` — never from a renderer. They are content (phrasing and display
52
52
  format), not markup, so they sit beside the kinds rather than inside the
53
- channel that used to own them. The output-parity golden asserts these strings
54
- byte-for-byte; a second copy of one is the exact failure this migration exists
53
+ channel that used to own them. `../__tests__/render-snapshot.test.ts` locks
54
+ these strings; a second copy of one is the exact failure this migration exists
55
55
  to prevent.
56
56
  - A file per kind, named after its old `email/render` counterpart. Kinds do not
57
57
  share a module — the point is that a kind is self-contained.
@@ -4,8 +4,7 @@
4
4
  * `renderToChannel` is the whole pipeline in one call: look the kind up in
5
5
  * `NOTIFICATION_DEFINITIONS`, `compose` its payload into channel-agnostic
6
6
  * `NotificationContent`, hand that to a `Renderer`. Every piece already existed
7
- * and is already proven byte-for-byte against the output-parity golden this
8
- * module is the wiring, not a rendering change.
7
+ * when this was written the module is the wiring, not a rendering change.
9
8
  *
10
9
  * What it deliberately does NOT do is dispatch. The old `renderEmail` was a
11
10
  * twelve-arm `switch` that threw on the one arm it did not have; here the
@@ -30,10 +29,11 @@
30
29
  * only to build one when the caller supplies none, and only via
31
30
  * `createRenderContext` — the single sanctioned clock read in this domain.
32
31
  * Pass a context to get reproducible bytes.
33
- * - Output MUST equal what the old email layer emitted, byte for byte, for every
34
- * fixture in `__tests__/output-parity.golden.ts`. That is what
35
- * `__tests__/output-parity.test.ts` asserts, and it is the only reason this
36
- * rewiring is safe. The golden is now the sole record of those bytes.
32
+ * - Output is locked by `__tests__/render-snapshot.test.ts`, across every fixture
33
+ * in `__tests__/fixtures.ts`. A diff there means a real sent email changed:
34
+ * review it, then regenerate. This rewiring was originally proven safe by
35
+ * byte-equality with the old email layer; that proof shipped in 35.0.0 and was
36
+ * retired with the golden (ADR-CONTRACTS-087).
37
37
  * - A kind's `defaults` layer UNDER the caller's wishes, never over them. A full
38
38
  * `RenderContext` has no gaps left to fill, so it wins outright; partial
39
39
  * overrides are filled from `defaults` first and the package default last.
@@ -48,12 +48,16 @@ expect to style it properly rather than trust the placeholder.
48
48
 
49
49
  ## Invariants
50
50
 
51
- - Output is byte-identical to what the old `src/email/render` emitted. This is
52
- the whole constraint of the migration. `__tests__/render.test.ts` proves it by
53
- composing every fixture in the frozen output-parity golden and rendering it
54
- through this channel a diff there means a real sent email changed. The golden
55
- is the only record of those bytes now, so it is the authority: fix the
56
- renderer, never the golden.
51
+ - Output is locked by `../../__tests__/render-snapshot.test.ts`, across every
52
+ fixture in `../../__tests__/fixtures.ts` a diff there means a real sent email
53
+ changed, so review it before regenerating. Byte-identity with the old
54
+ `src/email/render` was the whole constraint of the migration; it held, shipped
55
+ in 35.0.0, and ADR-CONTRACTS-087 retired the golden that proved it, because
56
+ that proof could not double as permission to change the design.
57
+ - `__tests__/render.test.ts` asserts the rules that outlive any redesign — every
58
+ element type supported, subject from the content's title, year and signer from
59
+ context, the `:` restored, user content escaped. Markup belongs in the
60
+ snapshot, not there.
57
61
  - PURE. `render` is a function of `(content, context)`: no clock, no environment,
58
62
  no I/O. The copyright year comes from `context.brand.copyrightYear`, which is
59
63
  what retires the `new Date().getFullYear()` hidden inside the old
@@ -4,26 +4,29 @@
4
4
 
5
5
  Tests for the email channel (ADR-CONTRACTS-086).
6
6
 
7
- `render.test.ts` holds the renderer's one load-bearing proof: every fixture in
8
- `../../../__tests__/output-parity.golden.ts`, composed through
9
- `NOTIFICATION_DEFINITIONS` and rendered through `emailRenderer`, byte-for-byte.
10
- The rest of the file pins the properties the golden cannot see that `supports`
11
- is total, that the year comes from `RenderContext`, that the `:` separator the
12
- content model drops is put back, and that user-controlled fields are escaped.
7
+ `render.test.ts` pins the renderer's behavioural rules the claims that hold no
8
+ matter how the email is styled: that `supports` is total, that the subject comes
9
+ from the content's title, that the year and signer come from `RenderContext`
10
+ rather than a clock, that the `:` separator the content model drops is put back,
11
+ and that user-controlled fields are escaped. It also renders every fixture in
12
+ `../../../__tests__/fixtures.ts` through `NOTIFICATION_DEFINITIONS`, which proves
13
+ each kind composes and renders on both surfaces at all.
14
+
15
+ The MARKUP is not here. `../../../__tests__/render-snapshot.test.ts` locks that.
13
16
 
14
17
  ## Invariants
15
18
 
16
- - The golden is the ORACLE, not this renderer's output. When the two disagree,
17
- the renderer is wrong: the golden records what the old `src/email/render`
18
- emitted at 32.0.0, and "no rendered email changed" is the migration's whole
19
- promise. It is never regenerated to make this file pass — and since that layer
20
- is now deleted, the golden is the only record of those bytes, so regenerating
21
- it destroys the evidence rather than updating it.
19
+ - Rules here, markup there. A test in this file that asserts what the HTML looks
20
+ like is in the wrong file: it belongs in the snapshot, where a redesign shows
21
+ up as a reviewable diff instead of a wall of broken assertions.
22
22
  - NO fake timers. The old layer's `signature()` read the wall clock, which is why
23
23
  every render test had to freeze it; here the year is a field on
24
24
  `RenderContext`, and keeping this file clock-free is the improvement being
25
25
  locked in.
26
- - Parity is asserted at two levels against one golden: here through the RENDERER
27
- (composed content bytes), and in `../../../__tests__/output-parity.test.ts`
28
- through the ENTRY POINT (`renderToChannel`, the whole pipeline). Either one
29
- drifting from the golden is a failure.
26
+ - This file used to assert every fixture byte-for-byte against a frozen golden of
27
+ what `src/email/render` emitted at 32.0.0 the migration's "no rendered email
28
+ changed" promise, checked here at the RENDERER and in a companion suite at the
29
+ ENTRY POINT (`renderToChannel`). The promise held and shipped in 35.0.0;
30
+ ADR-CONTRACTS-087 retired both, because byte-equality with a deleted layer
31
+ cannot survive email design being deliberately changed. The bytes are in git at
32
+ `v35.0.0`.
@@ -1,20 +1,19 @@
1
1
  /**
2
- * The email renderer's correctness proof (ADR-CONTRACTS-086).
2
+ * The email renderer's behavioural rules (ADR-CONTRACTS-086) — the claims that
3
+ * hold no matter how the email is styled: it supports every element type, takes
4
+ * the subject from the content's title, takes the year and signer from context
5
+ * rather than a clock, restores the `:` the content model does not carry, and
6
+ * escapes user-controlled text.
3
7
  *
4
- * The renderer has exactly one hard requirement: compose a kind, render it
5
- * through this channel, and get the bytes the old `src/email/render` produced.
6
- * So that is what this asserts — every fixture in the frozen output-parity
7
- * golden, composed via `NOTIFICATION_DEFINITIONS` and rendered through
8
- * `emailRenderer`, character-for-character.
9
- *
10
- * This does NOT replace `../../../__tests__/output-parity.test.ts`. That one
11
- * pins the ENTRY POINT (`renderToChannel`, the whole pipeline) against the same
12
- * golden; this pins the RENDERER alone, so a failure here says the markup moved
13
- * rather than that the wiring did. Two levels, one golden: either drifting is a
14
- * failure.
8
+ * The MARKUP is not asserted here. `../../../__tests__/render-snapshot.test.ts`
9
+ * locks that, as a reviewable regenerable diff. Until ADR-CONTRACTS-087 this
10
+ * file also asserted every fixture byte-for-byte against the 32.0.0 golden, to
11
+ * prove the multi-channel generalisation moved no bytes; that proof shipped in
12
+ * 35.0.0 and was retired with the golden, because email design is now allowed to
13
+ * change and byte-equality to a deleted layer cannot survive it being exercised.
15
14
  *
16
15
  * There is no fake timer here, and that is the improvement being locked in: the
17
- * year comes from `RenderContext`, so the golden's clock is just a number.
16
+ * year comes from `RenderContext`, so the fixtures' clock is just a number.
18
17
  */
19
18
 
20
19
  import { describe, expect, it } from "vitest";
@@ -26,23 +25,21 @@ import type {
26
25
  import type { RenderContext } from "../../../context";
27
26
  import { NOTIFICATION_DEFINITIONS } from "../../../registry";
28
27
  import {
29
- OUTPUT_PARITY_CLOCK,
30
- OUTPUT_PARITY_FIXTURES,
31
- OUTPUT_PARITY_GOLDEN,
28
+ FIXTURE_CLOCK,
29
+ NOTIFICATION_FIXTURES,
32
30
  fixtureKey,
33
- } from "../../../__tests__/output-parity.golden";
31
+ } from "../../../__tests__/fixtures";
34
32
  import { emailRenderer } from "../index";
35
33
 
36
34
  /**
37
- * The golden's context, stated rather than ticked. The old layer reached for
35
+ * The fixtures' context, stated rather than ticked. The old layer reached for
38
36
  * `new Date().getFullYear()` mid-render, which is why every render test had to
39
- * freeze the clock; here the year the golden was captured under is simply a
40
- * value.
37
+ * freeze the clock; here the year is simply a value.
41
38
  */
42
- const GOLDEN_CONTEXT: RenderContext = {
39
+ const FIXTURE_CONTEXT: RenderContext = {
43
40
  brand: {
44
41
  name: "Company Semantics",
45
- copyrightYear: new Date(OUTPUT_PARITY_CLOCK).getUTCFullYear(),
42
+ copyrightYear: new Date(FIXTURE_CLOCK).getUTCFullYear(),
46
43
  },
47
44
  };
48
45
 
@@ -77,7 +74,7 @@ describe("emailRenderer", () => {
77
74
  metadata: { kind: "org.invite", title: "Join Acme" },
78
75
  sections: [{ elements: [{ type: "body", text: "Hello." }] }],
79
76
  };
80
- expect(emailRenderer.render(content, GOLDEN_CONTEXT).subject).toBe(
77
+ expect(emailRenderer.render(content, FIXTURE_CONTEXT).subject).toBe(
81
78
  "Join Acme",
82
79
  );
83
80
  });
@@ -100,7 +97,7 @@ describe("emailRenderer", () => {
100
97
  metadata: { kind: "org.invite", title: "t" },
101
98
  sections: [{ elements: [{ type: "signature", signer: "Grace" }] }],
102
99
  };
103
- expect(emailRenderer.render(content, GOLDEN_CONTEXT).text).toContain(
100
+ expect(emailRenderer.render(content, FIXTURE_CONTEXT).text).toContain(
104
101
  "• Grace",
105
102
  );
106
103
  });
@@ -126,7 +123,7 @@ describe("emailRenderer", () => {
126
123
  },
127
124
  ],
128
125
  };
129
- const { text } = emailRenderer.render(content, GOLDEN_CONTEXT);
126
+ const { text } = emailRenderer.render(content, FIXTURE_CONTEXT);
130
127
  expect(text).toContain("Status: Valid");
131
128
  expect(text).toContain("Request details:");
132
129
  expect(text).toContain("IP address: 203.0.113.1");
@@ -141,7 +138,7 @@ describe("emailRenderer", () => {
141
138
  },
142
139
  ],
143
140
  };
144
- const { html } = emailRenderer.render(content, GOLDEN_CONTEXT);
141
+ const { html } = emailRenderer.render(content, FIXTURE_CONTEXT);
145
142
  expect(html).toContain("<script>");
146
143
  expect(html).not.toContain("<script>");
147
144
  });
@@ -153,27 +150,38 @@ describe("emailRenderer", () => {
153
150
  details: "5 codes in 2 minutes",
154
151
  timestamp: "2026-07-15T00:00:00.000Z",
155
152
  },
156
- GOLDEN_CONTEXT,
153
+ FIXTURE_CONTEXT,
157
154
  );
158
- expect(emailRenderer.render(content, GOLDEN_CONTEXT)).toEqual(
159
- emailRenderer.render(content, GOLDEN_CONTEXT),
155
+ expect(emailRenderer.render(content, FIXTURE_CONTEXT)).toEqual(
156
+ emailRenderer.render(content, FIXTURE_CONTEXT),
160
157
  );
161
158
  });
162
159
 
163
- describe("output parity with the email layer at 32.0.0", () => {
164
- for (const fixture of OUTPUT_PARITY_FIXTURES) {
160
+ describe("renders every fixture", () => {
161
+ // Was a byte-for-byte assertion against the 32.0.0 golden until
162
+ // ADR-CONTRACTS-087 retired it. The markup itself is locked by
163
+ // `../../../__tests__/render-snapshot.test.ts`; what is worth proving HERE
164
+ // is narrower and survives a redesign — that every kind in the registry
165
+ // composes and renders through this channel at all, on both surfaces. A kind
166
+ // whose `compose` reaches for a field its payload lacks fails here, at the
167
+ // renderer, rather than in whichever consumer sends it first.
168
+ for (const fixture of NOTIFICATION_FIXTURES) {
165
169
  const key = fixtureKey(fixture.kind, fixture.name);
166
- it(`${key} renders byte-for-byte`, () => {
170
+ it(`${key}`, () => {
167
171
  const definition = NOTIFICATION_DEFINITIONS[fixture.kind];
168
172
  // `payload as never`: the fixture call sites are type-checked in the
169
- // golden module; this only bridges compose's per-kind payload.
173
+ // fixtures module; this only bridges compose's per-kind payload.
170
174
  const content = definition.compose(
171
175
  fixture.payload as never,
172
- GOLDEN_CONTEXT,
176
+ FIXTURE_CONTEXT,
173
177
  );
174
- expect(emailRenderer.render(content, GOLDEN_CONTEXT)).toEqual(
175
- OUTPUT_PARITY_GOLDEN[key],
178
+ const { subject, text, html } = emailRenderer.render(
179
+ content,
180
+ FIXTURE_CONTEXT,
176
181
  );
182
+ expect(subject).toBeTruthy();
183
+ expect(text).toBeTruthy();
184
+ expect(html).toContain("<!DOCTYPE html>");
177
185
  });
178
186
  }
179
187
  });
@@ -8,8 +8,9 @@
8
8
  * the "⋮" dots.
9
9
  *
10
10
  * INVARIANTS:
11
- * - Pure, and byte-identical to what the deleted `src/email/render/blocks` emits. The
12
- * box art is real output: the output-parity golden asserts every column of it.
11
+ * - Pure. The box art is real output, and every column of it is locked by
12
+ * `../../__tests__/render-snapshot.test.ts` a stray space is a visibly
13
+ * broken email, not a whitespace nit.
13
14
  * - Both surfaces truncate at the same point — `clampMessage` is the one
14
15
  * truncation authority, and HTML and plain text both run content through it.
15
16
  */
@@ -33,6 +33,23 @@ export const MONO = `font-family: ${MONO_FONT_STACK};`;
33
33
  */
34
34
  export const FONT_SIZE = "13px";
35
35
 
36
+ /**
37
+ * The colour for a `warning` — the product's destructive token, resolved.
38
+ *
39
+ * This is `--destructive` from the app's `globals.css` `:root`
40
+ * (`oklch(0.577 0.245 27.325)`) converted to sRGB hex. It is COPIED, not
41
+ * imported, and it has to be: contracts sits ABOVE the app in the dependency
42
+ * flow, so this package cannot read the app's stylesheet — and email cannot read
43
+ * one either. There is no CSS to cascade from and no custom properties to
44
+ * resolve, so every colour in this channel is a literal in an inline `style`.
45
+ * If the token moves, this does not follow it; that divergence is the cost of
46
+ * the arrow pointing one way.
47
+ *
48
+ * The `:root` value rather than `.dark`'s, because this channel is light-only by
49
+ * construction — `./shells.ts` states `color: #1a1a1a` on the body.
50
+ */
51
+ export const DESTRUCTIVE = "#e7000b";
52
+
36
53
  /**
37
54
  * The gap AFTER a rendered line: `"normal"` is a blank line in plain text,
38
55
  * anything else is none.
@@ -11,10 +11,10 @@
11
11
  * - Pure. `render` is a function of `(content, context)` — no clock, no
12
12
  * environment, no I/O. The copyright year comes from
13
13
  * `context.brand.copyrightYear`.
14
- * - Output is byte-identical to what the old `src/email/render` emitted. That is
15
- * the whole constraint of this migration, and `__tests__/render.test.ts`
16
- * proves it against the frozen output-parity golden which, now that the old
17
- * layer is deleted, is the only surviving record of those bytes.
14
+ * - Output is locked by `../../__tests__/render-snapshot.test.ts`. Byte-identity
15
+ * with the old `src/email/render` was the whole constraint of the migration
16
+ * and it held; ADR-CONTRACTS-087 discharged that proof once it shipped, so
17
+ * this channel's markup is now free to change reviewably, via the snapshot.
18
18
  * - `supports` answers true for every element type: email is the rich channel,
19
19
  * and there is nothing in the content model it cannot depict.
20
20
  */
@@ -5,7 +5,9 @@
5
5
  * module offered templates a component per intent (`greeting()`, `keyValue()`,
6
6
  * `footer()`) and each template chose which to call; here the CONTENT names the
7
7
  * intent and this module is the only thing that knows the markup. The strings
8
- * are the same strings the output-parity golden asserts them byte-for-byte.
8
+ * started as the same strings, byte-for-byte; they are free to diverge now
9
+ * (ADR-CONTRACTS-087) and `../../__tests__/render-snapshot.test.ts` is what
10
+ * makes each divergence a reviewed one.
9
11
  *
10
12
  * **Spacing is derived, because the content model does not carry it.** The old
11
13
  * layer passed a `Spacing` per block, and `../../kinds` deliberately dropped it:
@@ -45,6 +47,7 @@ import type { RenderContext } from "../../context";
45
47
 
46
48
  import {
47
49
  COMPANY_URL,
50
+ DESTRUCTIVE,
48
51
  FONT_SIZE,
49
52
  MONO,
50
53
  SPACING,
@@ -151,8 +154,18 @@ export function renderElement(
151
154
  case "warning": {
152
155
  // Fieldless by design — the banner IS the content, and its wording is the
153
156
  // renderer's (see `../../content.ts`).
157
+ //
158
+ // The colour is this channel's answer to "this is a security notice", and
159
+ // only the HTML surface can give it: plain text has no colour, so there
160
+ // the banner's own glyphs carry the whole signal.
154
161
  const text = "🆆🅰🆁🅽🅸🅽🅶";
155
- return [paragraph(escapeHtml(text), text, trailing)];
162
+ return [
163
+ paragraph(
164
+ `<span style="color: ${DESTRUCTIVE};">${escapeHtml(text)}</span>`,
165
+ text,
166
+ trailing,
167
+ ),
168
+ ];
156
169
  }
157
170
 
158
171
  case "notice":
@@ -8,8 +8,9 @@
8
8
  *
9
9
  * INVARIANTS:
10
10
  * - Pure functions of their input lines. No clock, no environment, no I/O.
11
- * - Byte-identical to what that module emitted. These strings are the email's
12
- * actual markup; the output-parity golden asserts them character-for-character.
11
+ * - These strings are the email's actual markup, and
12
+ * `../../__tests__/render-snapshot.test.ts` asserts them
13
+ * character-for-character.
13
14
  */
14
15
 
15
16
  import { MONO, type Spacing } from "./constants";
@@ -14,8 +14,8 @@
14
14
  * returns a tag, the seam ADR-CONTRACTS-086 draws is back where it started.
15
15
  * - Pure. `formatExpiry` builds a `Date` from its ARGUMENT, which is not a clock
16
16
  * read; nothing here may add one.
17
- * - These strings are output. The output-parity golden asserts them
18
- * byte-for-byte, so a wording change here changes a real sent email.
17
+ * - These strings are output. `__tests__/render-snapshot.test.ts` locks them, so
18
+ * a wording change here changes a real sent email.
19
19
  */
20
20
 
21
21
  /** The standard "no action required" reassurance line. */