@echelon-foundry/design-system 0.3.0-main.217.1 → 0.3.0-main.219.1

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.
@@ -0,0 +1,103 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "contract": "forma.diagram-presentation",
4
+ "contractVersion": "2.0.0",
5
+ "stylesheet": "components.css",
6
+ "decision": "docs/decisions/ADR-0004-diagram-presentation-boundary.md",
7
+ "requirements": "requirements/OBJECT-METADATA-AND-DIAGRAM-PRESENTATION.md",
8
+ "ownership": {
9
+ "forma": ["surface", "node", "group", "connector", "connectorLabel", "marker", "relations", "legend", "valueState"],
10
+ "consumer": ["geometry", "routing", "semanticKind", "metadataValues", "metadataVisibility", "namedStyles", "paletteSlots", "metadataMappings", "appearancePrecedence", "membership"],
11
+ "editorOnly": ["selection", "handles", "guides", "routePreview", "marquee", "dragTargets", "connectionTargets", "minimap", "validationOverlay"],
12
+ "editorOnlyRule": "Editor-only adorners never use ef-diagram* classes and never appear in exported output."
13
+ },
14
+ "elements": {
15
+ "surface": { "element": "figure", "class": "ef-diagram", "parts": { "viewport": "ef-diagram__viewport", "canvas": "ef-diagram__canvas", "wires": "ef-diagram__wires" } },
16
+ "node": {
17
+ "element": "article",
18
+ "class": "ef-diagram-node",
19
+ "regions": { "kind": "ef-diagram-node__kind", "label": "ef-diagram-node__label", "metadata": "ef-diagram-node__meta" },
20
+ "shapeAttribute": "data-ef-shape",
21
+ "shapes": ["rectangle", "rounded", "pill", "ellipse", "diamond"],
22
+ "defaultShape": "rectangle",
23
+ "shapeNotes": { "diamond": "Square box (aspect-ratio 1); content sits in the inscribed square, so give it enough width for the label." },
24
+ "geometry": ["x", "y", "w", "h"],
25
+ "appearance": ["fill", "stroke", "accent", "foreground"]
26
+ },
27
+ "group": {
28
+ "element": "div",
29
+ "class": "ef-diagram-group",
30
+ "regions": { "header": "ef-diagram-group__header", "kind": "ef-diagram-group__kind", "label": "ef-diagram-group__label" },
31
+ "variantAttribute": "data-ef-group",
32
+ "variants": ["group", "lane", "phase"],
33
+ "defaultVariant": "group",
34
+ "lineAttribute": "data-ef-line",
35
+ "lineStyles": ["solid", "dashed", "dotted"],
36
+ "geometry": ["x", "y", "w", "h"],
37
+ "appearance": ["fill", "stroke", "accent", "foreground"],
38
+ "paintOrder": "Place groups first in the canvas so they paint beneath connectors and nodes.",
39
+ "membership": "Consumer data. Canvas overlap never implies membership."
40
+ },
41
+ "connector": {
42
+ "element": "path",
43
+ "class": "ef-diagram-connector",
44
+ "lineAttribute": "data-ef-line",
45
+ "lineStyles": ["solid", "dashed", "dotted"],
46
+ "defaultLineStyle": "solid",
47
+ "markers": { "class": "ef-diagram-marker", "kinds": ["arrow"], "attachment": ["marker-start", "marker-end"] },
48
+ "appearance": ["connectorStroke", "connectorWidth"]
49
+ },
50
+ "connectorLabel": { "element": "span", "class": "ef-diagram-connector__label", "geometry": ["x", "y"] },
51
+ "relations": { "element": "ol", "class": "ef-diagram__relations", "rule": "One text item per connector, in reading order." },
52
+ "legend": { "element": "dl", "class": "ef-diagram-legend", "swatch": "ef-diagram-legend__swatch", "appearance": ["fill", "stroke", "accent"] },
53
+ "valueState": {
54
+ "element": "span",
55
+ "class": "ef-diagram-value-state",
56
+ "stateAttribute": "data-ef-value-state",
57
+ "states": ["default", "derived", "source-bound", "unknown", "unavailable", "invalid"],
58
+ "explicit": "Explicit values carry no tag.",
59
+ "rule": "The tag text carries the meaning; the border style is secondary."
60
+ }
61
+ },
62
+ "appearanceProperties": {
63
+ "fill": { "customProperty": "--ef-diagram-fill", "appliesTo": ["node", "group", "legend"], "syntax": "color", "default": { "node": "--ef-color-surface-primary", "group": "transparent", "legend": "--ef-color-surface-primary" } },
64
+ "stroke": { "customProperty": "--ef-diagram-stroke", "appliesTo": ["node", "group", "legend"], "syntax": "color", "default": { "node": "--ef-color-border-functional", "group": "--ef-color-border-functional", "legend": "--ef-color-border-functional" }, "print": "Authored stroke prints; otherwise black." },
65
+ "accent": { "customProperty": "--ef-diagram-accent", "appliesTo": ["node", "group", "legend"], "syntax": "color", "default": { "node": "stroke", "group": "stroke", "legend": "stroke" } },
66
+ "foreground": { "customProperty": "--ef-diagram-foreground", "appliesTo": ["node", "group"], "syntax": "color", "default": { "node": "--ef-color-text-primary", "group": "--ef-color-text-primary" }, "contrast": "consumer" },
67
+ "connectorStroke": { "customProperty": "--ef-diagram-connector-stroke", "appliesTo": ["connector"], "syntax": "color", "default": { "connector": "--ef-color-border-functional" } },
68
+ "connectorWidth": { "customProperty": "--ef-diagram-connector-width", "appliesTo": ["connector"], "syntax": "number", "range": [1, 8], "default": { "connector": "2" } }
69
+ },
70
+ "geometryProperties": {
71
+ "x": "--ef-diagram-x",
72
+ "y": "--ef-diagram-y",
73
+ "w": "--ef-diagram-w",
74
+ "h": "--ef-diagram-h",
75
+ "canvasWidth": "--ef-diagram-canvas-w",
76
+ "canvasHeight": "--ef-diagram-canvas-h",
77
+ "unit": "px",
78
+ "axes": "physical (does not mirror in right-to-left documents)"
79
+ },
80
+ "color": {
81
+ "paint": "solid",
82
+ "literalSyntax": "#rrggbb",
83
+ "literalNote": "Consumers normalize literals to lowercase six-digit hex; alpha, gradients, blend modes and filters are not part of this contract (FMD-PAINT-001..003).",
84
+ "tokenReferences": [
85
+ "--ef-color-accent-primary",
86
+ "--ef-color-accent-secondary",
87
+ "--ef-color-border-functional",
88
+ "--ef-color-border-subtle",
89
+ "--ef-color-surface-primary",
90
+ "--ef-color-surface-secondary",
91
+ "--ef-color-surface-inverse",
92
+ "--ef-color-text-primary",
93
+ "--ef-color-text-secondary"
94
+ ],
95
+ "reset": "Remove the custom property to restore the Forma default (FMD-RESOLVE-006)."
96
+ },
97
+ "fallbacks": {
98
+ "forcedColors": "Nodes, groups, swatches and labels use Canvas/CanvasText; connectors and markers use CanvasText. Kind text, shape, line style and value-state text remain.",
99
+ "print": "Viewport stops scrolling; nodes avoid page breaks; authored strokes and accents print; unauthored boundaries print black.",
100
+ "backgroundsDisabled": "Every shape and group has a real border, so boundaries survive when fills are dropped.",
101
+ "grayscale": "Meaning is carried by kind text, labels, line style, the relationship list and the legend text, never by hue."
102
+ }
103
+ }
package/dist/all.css CHANGED
@@ -5079,6 +5079,8 @@
5079
5079
  * node: --ef-diagram-fill, --ef-diagram-stroke, --ef-diagram-accent,
5080
5080
  * --ef-diagram-foreground
5081
5081
  * connector: --ef-diagram-connector-stroke, --ef-diagram-connector-width
5082
+ * group: the node properties plus --ef-diagram-h (M2, forma#88)
5083
+ * The machine-readable contract is contracts/diagram-presentation.json.
5082
5084
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
5083
5085
  * the textual relationship list. Focus never reads authored color.
5084
5086
  */
@@ -5123,6 +5125,46 @@
5123
5125
  }
5124
5126
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
5125
5127
  .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
5128
+ /*
5129
+ * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
5130
+ * background-only shape) so the boundary survives backgrounds-off print and
5131
+ * forced colors. --ef-diagram-h is an optional minimum height.
5132
+ */
5133
+ .ef-diagram-node { min-block-size: var(--ef-diagram-h, auto); }
5134
+ .ef-diagram-node[data-ef-shape="ellipse"] {
5135
+ border-radius: 50%;
5136
+ border-inline-start-width: 2px;
5137
+ border-block-end: 6px solid var(--ef-diagram-node-accent);
5138
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
5139
+ place-content: center;
5140
+ text-align: center;
5141
+ }
5142
+ .ef-diagram-node[data-ef-shape="ellipse"] .ef-diagram-node__meta,
5143
+ .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
5144
+ /*
5145
+ * A square box whose ::before is the diamond; the content sits in the
5146
+ * inscribed square. Padding is derived from --ef-diagram-w because percentage
5147
+ * padding would resolve against the canvas, not the node.
5148
+ */
5149
+ .ef-diagram-node[data-ef-shape="diamond"] {
5150
+ aspect-ratio: 1;
5151
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
5152
+ border: 0;
5153
+ background: transparent;
5154
+ place-content: center;
5155
+ text-align: center;
5156
+ isolation: isolate;
5157
+ }
5158
+ .ef-diagram-node[data-ef-shape="diamond"]::before {
5159
+ content: "";
5160
+ position: absolute;
5161
+ inset: 14.645%;
5162
+ z-index: -1;
5163
+ transform: rotate(45deg);
5164
+ border: 2px solid var(--ef-diagram-node-stroke);
5165
+ border-left: 6px solid var(--ef-diagram-node-accent);
5166
+ background: var(--ef-diagram-node-fill);
5167
+ }
5126
5168
  .ef-diagram-node :where(h3, p, dl, dd) { margin: 0; color: inherit; max-inline-size: none; }
5127
5169
  .ef-diagram-node__label { font-size: 1rem; line-height: 1.25; }
5128
5170
  .ef-diagram-node a { color: inherit; }
@@ -5167,17 +5209,88 @@
5167
5209
  vertical-align: middle;
5168
5210
  }
5169
5211
 
5212
+ /*
5213
+ * Groups, lanes and phases (FMD-M2). A boundary with a visible text label,
5214
+ * painted beneath connectors and nodes (place it first in the canvas).
5215
+ * Membership is consumer data: overlap on the canvas never implies it, so the
5216
+ * consumer states membership in text (node metadata or the relationship list).
5217
+ * Kind is text in __kind; data-ef-group only selects the presentation.
5218
+ */
5219
+ .ef-diagram-group {
5220
+ --ef-diagram-group-fill: var(--ef-diagram-fill, transparent);
5221
+ --ef-diagram-group-stroke: var(--ef-diagram-stroke, var(--ef-color-border-functional, #3a403c));
5222
+ --ef-diagram-group-accent: var(--ef-diagram-accent, var(--ef-diagram-group-stroke));
5223
+ --ef-diagram-group-foreground: var(--ef-diagram-foreground, var(--ef-color-text-primary, #171a18));
5224
+ position: absolute;
5225
+ left: var(--ef-diagram-x, 0);
5226
+ top: var(--ef-diagram-y, 0);
5227
+ inline-size: var(--ef-diagram-w, 20rem);
5228
+ block-size: var(--ef-diagram-h, 10rem);
5229
+ box-sizing: border-box;
5230
+ margin: 0;
5231
+ border: 2px solid var(--ef-diagram-group-stroke);
5232
+ border-block-start: 6px solid var(--ef-diagram-group-accent);
5233
+ background: var(--ef-diagram-group-fill);
5234
+ color: var(--ef-diagram-group-foreground);
5235
+ }
5236
+ .ef-diagram-group[data-ef-line="dashed"] { border-style: dashed; border-block-start-style: solid; }
5237
+ .ef-diagram-group[data-ef-line="dotted"] { border-style: dotted; border-block-start-style: solid; }
5238
+ .ef-diagram-group[data-ef-group="lane"] { border-block-start-width: 2px; border-inline-start-width: 6px; border-inline-start-color: var(--ef-diagram-group-accent); border-inline-start-style: solid; }
5239
+ .ef-diagram-group__header {
5240
+ display: flex;
5241
+ flex-wrap: wrap;
5242
+ gap: 0 var(--ef-primitive-spacing-2, 0.5rem);
5243
+ align-items: baseline;
5244
+ margin: 0;
5245
+ padding: var(--ef-primitive-spacing-1, 0.25rem) var(--ef-primitive-spacing-2, 0.5rem);
5246
+ color: inherit;
5247
+ font-size: 0.875rem;
5248
+ overflow-wrap: anywhere;
5249
+ }
5250
+ .ef-diagram-group__kind { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
5251
+ .ef-diagram-group__label { font-weight: 700; }
5252
+
5253
+ /*
5254
+ * Metadata value state (FMD-SCHEMA-006/007). The state is text; the border
5255
+ * style is a secondary cue. Explicit values carry no tag.
5256
+ */
5257
+ .ef-diagram-value-state {
5258
+ display: inline-block;
5259
+ margin-inline-start: var(--ef-primitive-spacing-1, 0.25rem);
5260
+ padding: 0 var(--ef-primitive-spacing-1, 0.25rem);
5261
+ border: 1px dashed currentColor;
5262
+ font-size: 0.75rem;
5263
+ font-style: normal;
5264
+ font-weight: 400;
5265
+ line-height: 1.4;
5266
+ text-transform: none;
5267
+ letter-spacing: normal;
5268
+ vertical-align: baseline;
5269
+ }
5270
+ .ef-diagram-value-state[data-ef-value-state="unknown"],
5271
+ .ef-diagram-value-state[data-ef-value-state="unavailable"] { border-style: dotted; }
5272
+ .ef-diagram-value-state[data-ef-value-state="invalid"] { border-style: solid; border-width: 2px; font-weight: 700; }
5273
+ .ef-diagram-value-state[data-ef-value-state="source-bound"] { border-style: double; border-width: 3px; }
5274
+
5170
5275
  @media (forced-colors: active) {
5171
- .ef-diagram-node, .ef-diagram-legend__swatch { border-color: CanvasText; background: Canvas; color: CanvasText; }
5276
+ .ef-diagram-node, .ef-diagram-legend__swatch, .ef-diagram-group { border-color: CanvasText; background: Canvas; color: CanvasText; }
5277
+ .ef-diagram-node[data-ef-shape="diamond"] { background: transparent; }
5278
+ .ef-diagram-node[data-ef-shape="diamond"]::before { border-color: CanvasText; background: Canvas; }
5172
5279
  .ef-diagram-connector { stroke: CanvasText; }
5173
5280
  .ef-diagram-marker { fill: CanvasText; }
5174
5281
  .ef-diagram-connector__label { background: Canvas; color: CanvasText; }
5175
5282
  }
5176
5283
 
5177
- /* Backgrounds-off and grayscale print: boundaries, kinds and line styles remain. */
5284
+ /*
5285
+ * Print (color, grayscale and backgrounds-off). Authored strokes and accents
5286
+ * stay, since borders print even when backgrounds are disabled. Without an
5287
+ * authored stroke, boundaries print black. Kinds, labels, line styles and
5288
+ * the relationship list carry meaning when fills disappear.
5289
+ */
5178
5290
  @media print {
5179
5291
  .ef-diagram__viewport { overflow: visible; border-color: #000; }
5180
- .ef-diagram-node { break-inside: avoid; border-color: #000; }
5292
+ .ef-diagram-node { break-inside: avoid; --ef-diagram-node-stroke: var(--ef-diagram-stroke, #000); }
5293
+ .ef-diagram-group { --ef-diagram-group-stroke: var(--ef-diagram-stroke, #000); }
5181
5294
  .ef-diagram { break-inside: avoid; }
5182
5295
  }
5183
5296
 
@@ -177,12 +177,12 @@
177
177
  "tokens.css"
178
178
  ],
179
179
  "bytes": {
180
- "all.css": 300750,
180
+ "all.css": 306099,
181
181
  "assessment.css": 34482,
182
182
  "brands/echelon.css": 3621,
183
183
  "brands/example-harbor.css": 3637,
184
184
  "brands/index.json": 335,
185
- "components.css": 189629,
185
+ "components.css": 194978,
186
186
  "foundations.css": 10639,
187
187
  "marketing.css": 45073,
188
188
  "patterns/active-filter-summary.html": 532,
@@ -4340,6 +4340,8 @@
4340
4340
  * node: --ef-diagram-fill, --ef-diagram-stroke, --ef-diagram-accent,
4341
4341
  * --ef-diagram-foreground
4342
4342
  * connector: --ef-diagram-connector-stroke, --ef-diagram-connector-width
4343
+ * group: the node properties plus --ef-diagram-h (M2, forma#88)
4344
+ * The machine-readable contract is contracts/diagram-presentation.json.
4343
4345
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
4344
4346
  * the textual relationship list. Focus never reads authored color.
4345
4347
  */
@@ -4384,6 +4386,46 @@
4384
4386
  }
4385
4387
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
4386
4388
  .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
4389
+ /*
4390
+ * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
4391
+ * background-only shape) so the boundary survives backgrounds-off print and
4392
+ * forced colors. --ef-diagram-h is an optional minimum height.
4393
+ */
4394
+ .ef-diagram-node { min-block-size: var(--ef-diagram-h, auto); }
4395
+ .ef-diagram-node[data-ef-shape="ellipse"] {
4396
+ border-radius: 50%;
4397
+ border-inline-start-width: 2px;
4398
+ border-block-end: 6px solid var(--ef-diagram-node-accent);
4399
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
4400
+ place-content: center;
4401
+ text-align: center;
4402
+ }
4403
+ .ef-diagram-node[data-ef-shape="ellipse"] .ef-diagram-node__meta,
4404
+ .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
4405
+ /*
4406
+ * A square box whose ::before is the diamond; the content sits in the
4407
+ * inscribed square. Padding is derived from --ef-diagram-w because percentage
4408
+ * padding would resolve against the canvas, not the node.
4409
+ */
4410
+ .ef-diagram-node[data-ef-shape="diamond"] {
4411
+ aspect-ratio: 1;
4412
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
4413
+ border: 0;
4414
+ background: transparent;
4415
+ place-content: center;
4416
+ text-align: center;
4417
+ isolation: isolate;
4418
+ }
4419
+ .ef-diagram-node[data-ef-shape="diamond"]::before {
4420
+ content: "";
4421
+ position: absolute;
4422
+ inset: 14.645%;
4423
+ z-index: -1;
4424
+ transform: rotate(45deg);
4425
+ border: 2px solid var(--ef-diagram-node-stroke);
4426
+ border-left: 6px solid var(--ef-diagram-node-accent);
4427
+ background: var(--ef-diagram-node-fill);
4428
+ }
4387
4429
  .ef-diagram-node :where(h3, p, dl, dd) { margin: 0; color: inherit; max-inline-size: none; }
4388
4430
  .ef-diagram-node__label { font-size: 1rem; line-height: 1.25; }
4389
4431
  .ef-diagram-node a { color: inherit; }
@@ -4428,17 +4470,88 @@
4428
4470
  vertical-align: middle;
4429
4471
  }
4430
4472
 
4473
+ /*
4474
+ * Groups, lanes and phases (FMD-M2). A boundary with a visible text label,
4475
+ * painted beneath connectors and nodes (place it first in the canvas).
4476
+ * Membership is consumer data: overlap on the canvas never implies it, so the
4477
+ * consumer states membership in text (node metadata or the relationship list).
4478
+ * Kind is text in __kind; data-ef-group only selects the presentation.
4479
+ */
4480
+ .ef-diagram-group {
4481
+ --ef-diagram-group-fill: var(--ef-diagram-fill, transparent);
4482
+ --ef-diagram-group-stroke: var(--ef-diagram-stroke, var(--ef-color-border-functional, #3a403c));
4483
+ --ef-diagram-group-accent: var(--ef-diagram-accent, var(--ef-diagram-group-stroke));
4484
+ --ef-diagram-group-foreground: var(--ef-diagram-foreground, var(--ef-color-text-primary, #171a18));
4485
+ position: absolute;
4486
+ left: var(--ef-diagram-x, 0);
4487
+ top: var(--ef-diagram-y, 0);
4488
+ inline-size: var(--ef-diagram-w, 20rem);
4489
+ block-size: var(--ef-diagram-h, 10rem);
4490
+ box-sizing: border-box;
4491
+ margin: 0;
4492
+ border: 2px solid var(--ef-diagram-group-stroke);
4493
+ border-block-start: 6px solid var(--ef-diagram-group-accent);
4494
+ background: var(--ef-diagram-group-fill);
4495
+ color: var(--ef-diagram-group-foreground);
4496
+ }
4497
+ .ef-diagram-group[data-ef-line="dashed"] { border-style: dashed; border-block-start-style: solid; }
4498
+ .ef-diagram-group[data-ef-line="dotted"] { border-style: dotted; border-block-start-style: solid; }
4499
+ .ef-diagram-group[data-ef-group="lane"] { border-block-start-width: 2px; border-inline-start-width: 6px; border-inline-start-color: var(--ef-diagram-group-accent); border-inline-start-style: solid; }
4500
+ .ef-diagram-group__header {
4501
+ display: flex;
4502
+ flex-wrap: wrap;
4503
+ gap: 0 var(--ef-primitive-spacing-2, 0.5rem);
4504
+ align-items: baseline;
4505
+ margin: 0;
4506
+ padding: var(--ef-primitive-spacing-1, 0.25rem) var(--ef-primitive-spacing-2, 0.5rem);
4507
+ color: inherit;
4508
+ font-size: 0.875rem;
4509
+ overflow-wrap: anywhere;
4510
+ }
4511
+ .ef-diagram-group__kind { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
4512
+ .ef-diagram-group__label { font-weight: 700; }
4513
+
4514
+ /*
4515
+ * Metadata value state (FMD-SCHEMA-006/007). The state is text; the border
4516
+ * style is a secondary cue. Explicit values carry no tag.
4517
+ */
4518
+ .ef-diagram-value-state {
4519
+ display: inline-block;
4520
+ margin-inline-start: var(--ef-primitive-spacing-1, 0.25rem);
4521
+ padding: 0 var(--ef-primitive-spacing-1, 0.25rem);
4522
+ border: 1px dashed currentColor;
4523
+ font-size: 0.75rem;
4524
+ font-style: normal;
4525
+ font-weight: 400;
4526
+ line-height: 1.4;
4527
+ text-transform: none;
4528
+ letter-spacing: normal;
4529
+ vertical-align: baseline;
4530
+ }
4531
+ .ef-diagram-value-state[data-ef-value-state="unknown"],
4532
+ .ef-diagram-value-state[data-ef-value-state="unavailable"] { border-style: dotted; }
4533
+ .ef-diagram-value-state[data-ef-value-state="invalid"] { border-style: solid; border-width: 2px; font-weight: 700; }
4534
+ .ef-diagram-value-state[data-ef-value-state="source-bound"] { border-style: double; border-width: 3px; }
4535
+
4431
4536
  @media (forced-colors: active) {
4432
- .ef-diagram-node, .ef-diagram-legend__swatch { border-color: CanvasText; background: Canvas; color: CanvasText; }
4537
+ .ef-diagram-node, .ef-diagram-legend__swatch, .ef-diagram-group { border-color: CanvasText; background: Canvas; color: CanvasText; }
4538
+ .ef-diagram-node[data-ef-shape="diamond"] { background: transparent; }
4539
+ .ef-diagram-node[data-ef-shape="diamond"]::before { border-color: CanvasText; background: Canvas; }
4433
4540
  .ef-diagram-connector { stroke: CanvasText; }
4434
4541
  .ef-diagram-marker { fill: CanvasText; }
4435
4542
  .ef-diagram-connector__label { background: Canvas; color: CanvasText; }
4436
4543
  }
4437
4544
 
4438
- /* Backgrounds-off and grayscale print: boundaries, kinds and line styles remain. */
4545
+ /*
4546
+ * Print (color, grayscale and backgrounds-off). Authored strokes and accents
4547
+ * stay, since borders print even when backgrounds are disabled. Without an
4548
+ * authored stroke, boundaries print black. Kinds, labels, line styles and
4549
+ * the relationship list carry meaning when fills disappear.
4550
+ */
4439
4551
  @media print {
4440
4552
  .ef-diagram__viewport { overflow: visible; border-color: #000; }
4441
- .ef-diagram-node { break-inside: avoid; border-color: #000; }
4553
+ .ef-diagram-node { break-inside: avoid; --ef-diagram-node-stroke: var(--ef-diagram-stroke, #000); }
4554
+ .ef-diagram-group { --ef-diagram-group-stroke: var(--ef-diagram-stroke, #000); }
4442
4555
  .ef-diagram { break-inside: avoid; }
4443
4556
  }
4444
4557
 
@@ -5084,6 +5084,8 @@
5084
5084
  * node: --ef-diagram-fill, --ef-diagram-stroke, --ef-diagram-accent,
5085
5085
  * --ef-diagram-foreground
5086
5086
  * connector: --ef-diagram-connector-stroke, --ef-diagram-connector-width
5087
+ * group: the node properties plus --ef-diagram-h (M2, forma#88)
5088
+ * The machine-readable contract is contracts/diagram-presentation.json.
5087
5089
  * Non-color cues: the visible node kind text, data-ef-shape, data-ef-line and
5088
5090
  * the textual relationship list. Focus never reads authored color.
5089
5091
  */
@@ -5128,6 +5130,46 @@
5128
5130
  }
5129
5131
  .ef-diagram-node[data-ef-shape="rounded"] { border-radius: var(--ef-primitive-spacing-3, 0.75rem); }
5130
5132
  .ef-diagram-node[data-ef-shape="pill"] { border-radius: 999px; padding-inline: var(--ef-primitive-spacing-5, 1.5rem); }
5133
+ /*
5134
+ * FMD-M2 (forma#88). Ellipse and diamond keep a real border (never a
5135
+ * background-only shape) so the boundary survives backgrounds-off print and
5136
+ * forced colors. --ef-diagram-h is an optional minimum height.
5137
+ */
5138
+ .ef-diagram-node { min-block-size: var(--ef-diagram-h, auto); }
5139
+ .ef-diagram-node[data-ef-shape="ellipse"] {
5140
+ border-radius: 50%;
5141
+ border-inline-start-width: 2px;
5142
+ border-block-end: 6px solid var(--ef-diagram-node-accent);
5143
+ padding: var(--ef-primitive-spacing-4, 1rem) calc(var(--ef-diagram-w, 12rem) * 0.14);
5144
+ place-content: center;
5145
+ text-align: center;
5146
+ }
5147
+ .ef-diagram-node[data-ef-shape="ellipse"] .ef-diagram-node__meta,
5148
+ .ef-diagram-node[data-ef-shape="diamond"] .ef-diagram-node__meta { grid-template-columns: auto auto; justify-content: center; text-align: start; }
5149
+ /*
5150
+ * A square box whose ::before is the diamond; the content sits in the
5151
+ * inscribed square. Padding is derived from --ef-diagram-w because percentage
5152
+ * padding would resolve against the canvas, not the node.
5153
+ */
5154
+ .ef-diagram-node[data-ef-shape="diamond"] {
5155
+ aspect-ratio: 1;
5156
+ padding: calc(var(--ef-diagram-w, 12rem) * 0.2);
5157
+ border: 0;
5158
+ background: transparent;
5159
+ place-content: center;
5160
+ text-align: center;
5161
+ isolation: isolate;
5162
+ }
5163
+ .ef-diagram-node[data-ef-shape="diamond"]::before {
5164
+ content: "";
5165
+ position: absolute;
5166
+ inset: 14.645%;
5167
+ z-index: -1;
5168
+ transform: rotate(45deg);
5169
+ border: 2px solid var(--ef-diagram-node-stroke);
5170
+ border-left: 6px solid var(--ef-diagram-node-accent);
5171
+ background: var(--ef-diagram-node-fill);
5172
+ }
5131
5173
  .ef-diagram-node :where(h3, p, dl, dd) { margin: 0; color: inherit; max-inline-size: none; }
5132
5174
  .ef-diagram-node__label { font-size: 1rem; line-height: 1.25; }
5133
5175
  .ef-diagram-node a { color: inherit; }
@@ -5172,17 +5214,88 @@
5172
5214
  vertical-align: middle;
5173
5215
  }
5174
5216
 
5217
+ /*
5218
+ * Groups, lanes and phases (FMD-M2). A boundary with a visible text label,
5219
+ * painted beneath connectors and nodes (place it first in the canvas).
5220
+ * Membership is consumer data: overlap on the canvas never implies it, so the
5221
+ * consumer states membership in text (node metadata or the relationship list).
5222
+ * Kind is text in __kind; data-ef-group only selects the presentation.
5223
+ */
5224
+ .ef-diagram-group {
5225
+ --ef-diagram-group-fill: var(--ef-diagram-fill, transparent);
5226
+ --ef-diagram-group-stroke: var(--ef-diagram-stroke, var(--ef-color-border-functional, #3a403c));
5227
+ --ef-diagram-group-accent: var(--ef-diagram-accent, var(--ef-diagram-group-stroke));
5228
+ --ef-diagram-group-foreground: var(--ef-diagram-foreground, var(--ef-color-text-primary, #171a18));
5229
+ position: absolute;
5230
+ left: var(--ef-diagram-x, 0);
5231
+ top: var(--ef-diagram-y, 0);
5232
+ inline-size: var(--ef-diagram-w, 20rem);
5233
+ block-size: var(--ef-diagram-h, 10rem);
5234
+ box-sizing: border-box;
5235
+ margin: 0;
5236
+ border: 2px solid var(--ef-diagram-group-stroke);
5237
+ border-block-start: 6px solid var(--ef-diagram-group-accent);
5238
+ background: var(--ef-diagram-group-fill);
5239
+ color: var(--ef-diagram-group-foreground);
5240
+ }
5241
+ .ef-diagram-group[data-ef-line="dashed"] { border-style: dashed; border-block-start-style: solid; }
5242
+ .ef-diagram-group[data-ef-line="dotted"] { border-style: dotted; border-block-start-style: solid; }
5243
+ .ef-diagram-group[data-ef-group="lane"] { border-block-start-width: 2px; border-inline-start-width: 6px; border-inline-start-color: var(--ef-diagram-group-accent); border-inline-start-style: solid; }
5244
+ .ef-diagram-group__header {
5245
+ display: flex;
5246
+ flex-wrap: wrap;
5247
+ gap: 0 var(--ef-primitive-spacing-2, 0.5rem);
5248
+ align-items: baseline;
5249
+ margin: 0;
5250
+ padding: var(--ef-primitive-spacing-1, 0.25rem) var(--ef-primitive-spacing-2, 0.5rem);
5251
+ color: inherit;
5252
+ font-size: 0.875rem;
5253
+ overflow-wrap: anywhere;
5254
+ }
5255
+ .ef-diagram-group__kind { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
5256
+ .ef-diagram-group__label { font-weight: 700; }
5257
+
5258
+ /*
5259
+ * Metadata value state (FMD-SCHEMA-006/007). The state is text; the border
5260
+ * style is a secondary cue. Explicit values carry no tag.
5261
+ */
5262
+ .ef-diagram-value-state {
5263
+ display: inline-block;
5264
+ margin-inline-start: var(--ef-primitive-spacing-1, 0.25rem);
5265
+ padding: 0 var(--ef-primitive-spacing-1, 0.25rem);
5266
+ border: 1px dashed currentColor;
5267
+ font-size: 0.75rem;
5268
+ font-style: normal;
5269
+ font-weight: 400;
5270
+ line-height: 1.4;
5271
+ text-transform: none;
5272
+ letter-spacing: normal;
5273
+ vertical-align: baseline;
5274
+ }
5275
+ .ef-diagram-value-state[data-ef-value-state="unknown"],
5276
+ .ef-diagram-value-state[data-ef-value-state="unavailable"] { border-style: dotted; }
5277
+ .ef-diagram-value-state[data-ef-value-state="invalid"] { border-style: solid; border-width: 2px; font-weight: 700; }
5278
+ .ef-diagram-value-state[data-ef-value-state="source-bound"] { border-style: double; border-width: 3px; }
5279
+
5175
5280
  @media (forced-colors: active) {
5176
- .ef-diagram-node, .ef-diagram-legend__swatch { border-color: CanvasText; background: Canvas; color: CanvasText; }
5281
+ .ef-diagram-node, .ef-diagram-legend__swatch, .ef-diagram-group { border-color: CanvasText; background: Canvas; color: CanvasText; }
5282
+ .ef-diagram-node[data-ef-shape="diamond"] { background: transparent; }
5283
+ .ef-diagram-node[data-ef-shape="diamond"]::before { border-color: CanvasText; background: Canvas; }
5177
5284
  .ef-diagram-connector { stroke: CanvasText; }
5178
5285
  .ef-diagram-marker { fill: CanvasText; }
5179
5286
  .ef-diagram-connector__label { background: Canvas; color: CanvasText; }
5180
5287
  }
5181
5288
 
5182
- /* Backgrounds-off and grayscale print: boundaries, kinds and line styles remain. */
5289
+ /*
5290
+ * Print (color, grayscale and backgrounds-off). Authored strokes and accents
5291
+ * stay, since borders print even when backgrounds are disabled. Without an
5292
+ * authored stroke, boundaries print black. Kinds, labels, line styles and
5293
+ * the relationship list carry meaning when fills disappear.
5294
+ */
5183
5295
  @media print {
5184
5296
  .ef-diagram__viewport { overflow: visible; border-color: #000; }
5185
- .ef-diagram-node { break-inside: avoid; border-color: #000; }
5297
+ .ef-diagram-node { break-inside: avoid; --ef-diagram-node-stroke: var(--ef-diagram-stroke, #000); }
5298
+ .ef-diagram-group { --ef-diagram-group-stroke: var(--ef-diagram-stroke, #000); }
5186
5299
  .ef-diagram { break-inside: avoid; }
5187
5300
  }
5188
5301
 
@@ -42,8 +42,8 @@
42
42
  {
43
43
  "file": "forma-all.css",
44
44
  "description": "Complete Forma surface (application components, assessment, and marketing layers) for sites that also present Forma application patterns such as data grids or status lozenges.",
45
- "bytes": 301112,
46
- "sha256": "06a5a33e06a5aefd7dead874c259c67611907074ec95346d30e0cdb57d18ea37",
45
+ "bytes": 306461,
46
+ "sha256": "976b95c7e990ecc746f627be981a6d91721132ef8d242cd60992df94dbfccb24",
47
47
  "sources": [
48
48
  "dist/all.css"
49
49
  ]
@@ -1,4 +1,4 @@
1
1
  9aec7b369ef6b60ceb5c8db51f088d232d2da07816cf9074ef1859fe035f4a65 forma-marketing.css
2
2
  157f1ec870a8825a0bbbfed4e719db7b92962e2625a8165f73abc4214c01e907 forma-marketing-theme-echelon.css
3
3
  383948a077c070ad4bda90688a84f65bd319fde1e326197d5917f0dde632f947 forma-echelon-marketing.css
4
- 06a5a33e06a5aefd7dead874c259c67611907074ec95346d30e0cdb57d18ea37 forma-all.css
4
+ 976b95c7e990ecc746f627be981a6d91721132ef8d242cd60992df94dbfccb24 forma-all.css
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echelon-foundry/design-system",
3
- "version": "0.3.0-main.217.1",
3
+ "version": "0.3.0-main.219.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Zero-runtime HTML and CSS design system for Echelon Foundry applications.",
@@ -23,7 +23,8 @@
23
23
  "./marketing.css": "./dist/marketing.css",
24
24
  "./marketing/*": "./dist/marketing/*",
25
25
  "./tokens.json": "./tokens/echelon.tokens.json",
26
- "./figma/components.json": "./figma/component-contracts.json"
26
+ "./figma/components.json": "./figma/component-contracts.json",
27
+ "./contracts/diagram-presentation.json": "./contracts/diagram-presentation.json"
27
28
  },
28
29
  "files": [
29
30
  "dist",
@@ -32,7 +33,8 @@
32
33
  "docs/BRANDING.md",
33
34
  "docs/MARKETING-SITES.md",
34
35
  "tokens/echelon.tokens.json",
35
- "figma/component-contracts.json"
36
+ "figma/component-contracts.json",
37
+ "contracts/diagram-presentation.json"
36
38
  ],
37
39
  "scripts": {
38
40
  "clean": "node tools/clean.mjs",
@@ -52,7 +54,7 @@
52
54
  "site:mobile": "playwright test --config=playwright.site.config.mjs",
53
55
  "pack:verify": "npm run build && npm pack --dry-run",
54
56
  "test:package": "node --test tests/package-contract.test.mjs",
55
- "test:css": "node --test tests/css-source.test.mjs",
57
+ "test:css": "node --test tests/css-source.test.mjs tests/diagram-contract.test.mjs",
56
58
  "brands:build": "dotnet run --project tools/BrandCompiler/BrandCompiler.fsproj -- brands dist/brands",
57
59
  "test:brands": "node --test tests/brand-compiler.test.mjs",
58
60
  "test:character-grid": "node --test tests/character-grid.test.mjs",