@ashley-shrok/viewmodel-shell 5.2.0 → 6.0.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/dist/browser.js CHANGED
@@ -2880,10 +2880,12 @@ export class BrowserAdapter {
2880
2880
  bar.className = "vms-stat-bar";
2881
2881
  n.stats.forEach(stat => {
2882
2882
  const item = document.createElement("div");
2883
- item.className = "vms-stat-bar__item";
2883
+ item.className = stat.tone
2884
+ ? `vms-stat-bar__item vms-stat-bar__item--toned vms-stat-bar__item--tone-${stat.tone}`
2885
+ : "vms-stat-bar__item";
2884
2886
  const val = document.createElement("span");
2885
2887
  val.className = "vms-stat-bar__value";
2886
- val.textContent = String(stat.value);
2888
+ val.textContent = stat.value;
2887
2889
  const lbl = document.createElement("span");
2888
2890
  lbl.className = "vms-stat-bar__label";
2889
2891
  lbl.textContent = stat.label;
@@ -3405,7 +3407,9 @@ export class BrowserAdapter {
3405
3407
  n.steps.forEach((step, i) => {
3406
3408
  const state = i < n.current ? "done" : i === n.current ? "current" : "upcoming";
3407
3409
  const li = document.createElement("li");
3408
- li.className = `vms-steps__step vms-steps__step--${state}`;
3410
+ li.className = step.tone
3411
+ ? `vms-steps__step vms-steps__step--${state} vms-steps__step--toned vms-steps__step--tone-${step.tone}`
3412
+ : `vms-steps__step vms-steps__step--${state}`;
3409
3413
  if (state === "current")
3410
3414
  li.setAttribute("aria-current", "step");
3411
3415
  // Connector — CSS-drawn line marker-center to marker-center, behind the
package/dist/index.d.ts CHANGED
@@ -722,7 +722,21 @@ export interface StatBarNode {
722
722
  type: "stat-bar";
723
723
  stats: Array<{
724
724
  label: string;
725
- value: string | number;
725
+ /** The stat's value. Typed `string` (not `string | number`) so the TS and
726
+ * .NET backends emit BYTE-IDENTICAL wire — a bare number would serialize as
727
+ * JSON `12` in TS but the .NET twin's `string Value` can only emit `"12"`,
728
+ * a real cross-backend drift the parity suite never exercised. Format the
729
+ * number server-side (`String(n)`, `n.toFixed(2)`, `$${n}`) — the value is
730
+ * display text, and any formatting an app wants is richer than a bare
731
+ * number anyway. (Narrowed in 6.0.0 — see CHANGELOG; the field was unused.) */
732
+ value: string;
733
+ /** Optional semantic status tone for this tile — the universal status color
734
+ * axis (same closed set as Section/Button/ListItem/TableRow). Renders the
735
+ * tile as a subtly tinted chip (surface tint + colored border, reusing the
736
+ * --vms-error/-warning/-success/-info tokens), so an unhealthy stat reads at
737
+ * a glance rather than via one small line of text. Omitted = today's neutral
738
+ * inline stat. Closed union. */
739
+ tone?: "danger" | "warning" | "success" | "info";
726
740
  }>;
727
741
  }
728
742
  export interface TabsNode {
@@ -993,6 +1007,16 @@ export interface StepItem {
993
1007
  label: string;
994
1008
  /** Optional one-line supporting text shown beside/under the label. */
995
1009
  description?: string;
1010
+ /** Optional semantic status tone for this step — the universal status color
1011
+ * axis (same closed set as Section/Button/ListItem/TableRow). ORTHOGONAL to
1012
+ * the done/current/upcoming state the framework DERIVES from `current`: tone
1013
+ * overlays a semantic color onto the marker (e.g. a failed/blocked stage read
1014
+ * as `danger`, a stage needing attention as `warning`) regardless of its
1015
+ * position in the sequence. Omitted = the derived state's default appearance
1016
+ * (accent for done/current, muted for upcoming). Because tone is app-authored
1017
+ * status reinforced by the step's own label text, it is NOT color-only (same
1018
+ * posture as Section tone). Closed union. */
1019
+ tone?: "danger" | "warning" | "success" | "info";
996
1020
  }
997
1021
  /** A discrete step / stepper / wizard progress indicator — an ordered list of
998
1022
  * stages with a single 0-based `current` index. Per-step status DERIVES from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashley-shrok/viewmodel-shell",
3
- "version": "5.2.0",
3
+ "version": "6.0.0",
4
4
  "description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic \u2014 a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,6 +31,8 @@
31
31
  "scripts": {
32
32
  "build": "tsc -b tsconfig.tui.json",
33
33
  "check:core-globals": "node scripts/check-core-platform-globals.mjs",
34
+ "check:test-types": "tsc -p tsconfig.test.json --noEmit",
35
+ "check:demo-types": "node scripts/check-demo-types.mjs",
34
36
  "check:aa-contrast": "node scripts/check-aa-contrast.mjs",
35
37
  "check:no-demo-style": "node scripts/check-no-demo-style.mjs",
36
38
  "check:theme-byte-identity": "node scripts/check-theme-byte-identity.mjs",
@@ -29,6 +29,21 @@
29
29
  --vms-error: #c2453d;
30
30
  --vms-error-glow: rgba(194, 69, 61, 0.10);
31
31
  --vms-warning: #8a630d;
32
+ /* Warning is the ONE tone with a polarity split. --vms-warning is deliberately
33
+ DARK on light themes because it doubles as a TEXT/border color (.vms-text--warning,
34
+ button/badge outline) and warning-colored TEXT on a light surface must be dark to
35
+ clear WCAG-AA — a bright yellow text on white is unreadable. But a warning FILL has
36
+ no such constraint: the standard design-system pattern (Bootstrap/Material) is a
37
+ BRIGHT yellow fill with a DARK foreground. --vms-warning-fill + --vms-on-warning-fill
38
+ are that pair — used on every SOLID warning fill (toast, primary badge/button, the
39
+ toned steps marker) so warning actually reads as a yellow that grabs attention on
40
+ light themes, while staying AA (dark #2a2205 on #e0a823 = 7.37:1). Theme-independent
41
+ (a solid amber fill reads the same on any page surface); dark themes need no override
42
+ — their --vms-warning is already #e0a823, so this ALSO fixes the white-on-bright-amber
43
+ fill that failed AA on dark themes (2.14:1). Tints (section/table/chip) keep the base
44
+ --vms-warning as their own consistent family. */
45
+ --vms-warning-fill: #e0a823;
46
+ --vms-on-warning-fill: #2a2205;
32
47
  --vms-priority-high: #d76410;
33
48
  --vms-success: #2da359;
34
49
  --vms-info: #2277dd;
@@ -473,6 +488,32 @@ body:has(.vms-page--fill) { margin: 0; }
473
488
  text-transform: uppercase;
474
489
  letter-spacing: 0.05em;
475
490
  }
491
+ /* Per-tile tone (StatItem.tone) — a toned stat becomes a subtly tinted chip
492
+ (surface tint + colored border, the SAME color-mix formula as Section tone, so
493
+ it inherits that surface's already-AA-cleared text contrast) so an unhealthy
494
+ stat reads at a glance. Only toned items get the chip surface; neutral items
495
+ stay byte-identical to today's inline stat. --_tone resolves per tone below. */
496
+ .vms-stat-bar__item--tone-danger { --_tone: var(--vms-error); }
497
+ .vms-stat-bar__item--tone-warning { --_tone: var(--vms-warning); }
498
+ .vms-stat-bar__item--tone-success { --_tone: var(--vms-success); }
499
+ .vms-stat-bar__item--tone-info { --_tone: var(--vms-info); }
500
+ .vms-stat-bar__item--toned {
501
+ align-items: center;
502
+ gap: var(--vms-space-2xs);
503
+ padding: var(--vms-space-2xs) var(--vms-space-sm);
504
+ border: 1px solid var(--_tone);
505
+ border-radius: var(--vms-radius-md);
506
+ background: color-mix(in srgb, var(--_tone) 14%, var(--vms-surface));
507
+ }
508
+ /* The tint + border carry the status (the Section-tone model); the value + label
509
+ stay NEUTRAL and readable rather than tone-colored — tone text on its own 14%
510
+ tint fails WCAG-AA (success bottomed at 2.79:1 across the light themes), while
511
+ neutral text on the tint clears it exactly as Section tone's neutral text does. */
512
+ .vms-stat-bar__item--toned .vms-stat-bar__value { color: var(--vms-text); }
513
+ /* Label strengthens from muted to full text on a toned chip: muted on the 14%
514
+ tint bottoms at 4.21:1 (just under the 4.5 AA-text bar), and an emphasized chip
515
+ reads better with a solid label anyway. Full text on the tint is 11.14:1. */
516
+ .vms-stat-bar__item--toned .vms-stat-bar__label { color: var(--vms-text); }
476
517
 
477
518
  /* ── Form ── */
478
519
  .vms-form { display: flex; flex-direction: column; gap: var(--vms-space-sm); align-items: stretch; }
@@ -875,6 +916,16 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
875
916
  border-color: var(--_btn-tone);
876
917
  color: #fff;
877
918
  }
919
+ /* Warning is the polarity exception: a filled warning button uses the bright fill +
920
+ dark label (white on warning fails AA — 3.2/2.14:1 light/dark; see --vms-warning-fill).
921
+ Border matches the fill (no rim) — same as every other primary tone, which is the
922
+ conventional filled-warning look (cf. Bootstrap .btn-warning). The dark label + filled
923
+ shape identify the control; a dark rim read as out-of-place next to danger/success/info. */
924
+ .vms-button--warning.vms-button--primary {
925
+ background: var(--vms-warning-fill);
926
+ border-color: var(--vms-warning-fill);
927
+ color: var(--vms-on-warning-fill);
928
+ }
878
929
  .vms-button--primary:hover { filter: brightness(1.1); }
879
930
  .vms-button--secondary {
880
931
  background: transparent;
@@ -1328,7 +1379,10 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
1328
1379
  success/info are deepened (color-mix w/ black) so white at 14px also clears
1329
1380
  4.5:1 (their pure tokens sit at 3.2 / 4.4:1). */
1330
1381
  .vms-toast--danger { background: var(--vms-error); color: #fff; }
1331
- .vms-toast--warning { background: var(--vms-warning); color: #fff; }
1382
+ /* Warning uses the bright fill + dark foreground (see --vms-warning-fill): white on
1383
+ the dark #8a630d was muddy on light and white on the bright #e0a823 fails AA on dark
1384
+ (2.14:1). Dark text on the bright fill is 7.37:1 and reads as an attention-grabbing yellow. */
1385
+ .vms-toast--warning { background: var(--vms-warning-fill); color: var(--vms-on-warning-fill); }
1332
1386
  .vms-toast--success { background: color-mix(in srgb, var(--vms-success) 80%, #000); color: #fff; }
1333
1387
  .vms-toast--info { background: color-mix(in srgb, var(--vms-info) 88%, #000); color: #fff; }
1334
1388
  /* fade-out applied just before removal. */
@@ -1403,6 +1457,14 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
1403
1457
  border-color: var(--_badge-tone);
1404
1458
  color: #fff;
1405
1459
  }
1460
+ /* Warning polarity exception: a filled warning badge uses the bright fill + dark text
1461
+ (white on warning fails AA; see --vms-warning-fill). Border matches the fill (no rim),
1462
+ like every other primary tone. */
1463
+ .vms-badge--warning.vms-badge--primary {
1464
+ background: var(--vms-warning-fill);
1465
+ border-color: var(--vms-warning-fill);
1466
+ color: var(--vms-on-warning-fill);
1467
+ }
1406
1468
  .vms-badge--secondary {
1407
1469
  background: transparent;
1408
1470
  border-color: var(--_badge-tone);
@@ -1530,6 +1592,52 @@ button.vms-breadcrumb__link {
1530
1592
  }
1531
1593
  .vms-steps__label { color: var(--vms-text); font-weight: 600; }
1532
1594
  .vms-steps__description { color: var(--vms-text-muted); font-size: var(--vms-text-xs); }
1595
+ /* Per-step tone (StepItem.tone) — overlays the universal status color onto the
1596
+ marker, ORTHOGONAL to the derived done/current/upcoming treatment: the tone
1597
+ recolors whatever the state already does (filled states fill in the tone;
1598
+ upcoming stays outlined but in the tone), so a failed/blocked stage reads red
1599
+ without losing the reached/not-reached distinction. Two-class selectors
1600
+ outspecify the base state rules. The knockout glyph stays --vms-surface
1601
+ (polarity-adaptive, >=3:1 on the filled tone tokens across all themes — a
1602
+ graphical indicator whose state also rides aria-label + glyph + label text). */
1603
+ .vms-steps__step--tone-danger { --_tone: var(--vms-error); }
1604
+ .vms-steps__step--tone-warning { --_tone: var(--vms-warning); }
1605
+ .vms-steps__step--tone-success { --_tone: var(--vms-success); }
1606
+ .vms-steps__step--tone-info { --_tone: var(--vms-info); }
1607
+ /* Tone recolors the DERIVED treatment: filled states (done/current) fill in the tone;
1608
+ upcoming stays outlined but in the tone — preserving the reached/not-reached read while
1609
+ overlaying status color. Two-class selectors outspecify the base state rules. */
1610
+ .vms-steps__step--toned.vms-steps__step--done .vms-steps__marker,
1611
+ .vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
1612
+ background: var(--_tone);
1613
+ border-color: var(--_tone);
1614
+ color: var(--vms-surface);
1615
+ }
1616
+ .vms-steps__step--toned.vms-steps__step--upcoming .vms-steps__marker {
1617
+ border-color: var(--_tone);
1618
+ color: var(--_tone);
1619
+ }
1620
+ .vms-steps__step--toned.vms-steps__step--current .vms-steps__marker {
1621
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--_tone) 30%, transparent);
1622
+ }
1623
+ /* Warning FILLED (done/current): bright fill + dark glyph, no rim (see --vms-warning-fill). */
1624
+ .vms-steps__step--tone-warning.vms-steps__step--done .vms-steps__marker,
1625
+ .vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
1626
+ background: var(--vms-warning-fill);
1627
+ border-color: var(--vms-warning-fill);
1628
+ color: var(--vms-on-warning-fill);
1629
+ }
1630
+ /* Warning UPCOMING (outline): try the BRIGHT amber for the ring + number rather than the
1631
+ dark --vms-warning. Bright amber as a line/digit on white is ~2.14:1 (below the strict
1632
+ bar), so this is a judgment call on visual legibility — the step's state is multi-channel
1633
+ (aria-label + number + position), so color is not its sole carrier. */
1634
+ .vms-steps__step--tone-warning.vms-steps__step--upcoming .vms-steps__marker {
1635
+ border-color: var(--vms-warning-fill);
1636
+ color: var(--vms-warning-fill);
1637
+ }
1638
+ .vms-steps__step--tone-warning.vms-steps__step--current .vms-steps__marker {
1639
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--vms-warning-fill) 35%, transparent);
1640
+ }
1533
1641
 
1534
1642
  /* Connector — the OUTGOING segment from this marker toward the next (hidden on
1535
1643
  the last step). Colored by the step's own state: accent once the step is done,