eidotter 0.38.1 → 0.40.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 (121) hide show
  1. package/README.md +19 -2
  2. package/dist/components/Brand/components/Mark.d.ts +55 -0
  3. package/dist/components/Brand/components/Mark.js +87 -0
  4. package/dist/components/Brand/components/index.d.ts +4 -0
  5. package/dist/components/Brand/components/index.js +2 -0
  6. package/dist/components/Brand/components/markGeometry.d.ts +26 -0
  7. package/dist/components/Brand/components/markGeometry.js +54 -0
  8. package/dist/components/Brand/components/markGeometry.test.d.ts +1 -0
  9. package/dist/components/Breadcrumb/components/Breadcrumb.js +7 -3
  10. package/dist/components/Connector/components/Connector.d.ts +41 -0
  11. package/dist/components/Connector/components/Connector.js +165 -0
  12. package/dist/components/Connector/components/connectorGeometry.d.ts +50 -0
  13. package/dist/components/Connector/components/connectorGeometry.js +102 -0
  14. package/dist/components/Connector/components/connectorGeometry.test.d.ts +1 -0
  15. package/dist/components/Connector/components/index.d.ts +4 -0
  16. package/dist/components/Connector/components/index.js +2 -0
  17. package/dist/components/Connector/index.d.ts +2 -0
  18. package/dist/components/Connector/index.js +1 -0
  19. package/dist/components/DecayStrip/components/DecayStrip.d.ts +60 -0
  20. package/dist/components/DecayStrip/components/DecayStrip.js +99 -0
  21. package/dist/components/DecayStrip/components/index.d.ts +3 -0
  22. package/dist/components/DecayStrip/components/index.js +1 -0
  23. package/dist/components/DecayStrip/components/resample.d.ts +34 -0
  24. package/dist/components/DecayStrip/components/resample.js +44 -0
  25. package/dist/components/DecayStrip/components/resample.test.d.ts +1 -0
  26. package/dist/components/DecayStrip/index.d.ts +2 -0
  27. package/dist/components/DecayStrip/index.js +1 -0
  28. package/dist/components/Footer/components/Footer.js +6 -1
  29. package/dist/components/Gauge/components/Gauge.d.ts +43 -0
  30. package/dist/components/Gauge/components/Gauge.js +70 -0
  31. package/dist/components/Gauge/components/index.d.ts +2 -0
  32. package/dist/components/Gauge/components/index.js +1 -0
  33. package/dist/components/Gauge/index.d.ts +2 -0
  34. package/dist/components/Gauge/index.js +1 -0
  35. package/dist/components/Header/components/Header.d.ts +31 -0
  36. package/dist/components/Header/components/Header.js +14 -3
  37. package/dist/components/Header/components/HeaderContextRow.d.ts +33 -0
  38. package/dist/components/Header/components/HeaderContextRow.js +58 -0
  39. package/dist/components/Header/components/index.d.ts +1 -1
  40. package/dist/components/Header/index.d.ts +1 -1
  41. package/dist/components/Icon/components/Icon.d.ts +7 -0
  42. package/dist/components/Icon/components/Icon.js +9 -0
  43. package/dist/components/Icon/components/index.d.ts +1 -1
  44. package/dist/components/Icon/components/index.js +1 -1
  45. package/dist/components/InlineExpand/components/InlineExpand.d.ts +5 -3
  46. package/dist/components/InlineExpand/components/InlineExpand.js +15 -13
  47. package/dist/components/InlineLink/components/InlineLink.js +5 -9
  48. package/dist/components/Meter/components/Meter.d.ts +45 -0
  49. package/dist/components/Meter/components/Meter.js +79 -0
  50. package/dist/components/Meter/components/MeterGroup.d.ts +26 -0
  51. package/dist/components/Meter/components/MeterGroup.js +28 -0
  52. package/dist/components/Meter/components/index.d.ts +4 -0
  53. package/dist/components/Meter/components/index.js +2 -0
  54. package/dist/components/Meter/index.d.ts +2 -0
  55. package/dist/components/Meter/index.js +1 -0
  56. package/dist/components/Nav/components/Nav.js +14 -2
  57. package/dist/components/ProgressRing/components/ProgressRing.d.ts +5 -0
  58. package/dist/components/ProgressRing/components/ProgressRing.js +10 -2
  59. package/dist/components/RangeBar/components/RangeBar.d.ts +50 -0
  60. package/dist/components/RangeBar/components/RangeBar.js +90 -0
  61. package/dist/components/RangeBar/components/index.d.ts +2 -0
  62. package/dist/components/RangeBar/components/index.js +1 -0
  63. package/dist/components/RangeBar/index.d.ts +2 -0
  64. package/dist/components/RangeBar/index.js +1 -0
  65. package/dist/components/SeriesBars/components/SeriesBars.d.ts +43 -0
  66. package/dist/components/SeriesBars/components/SeriesBars.js +64 -0
  67. package/dist/components/SeriesBars/components/index.d.ts +2 -0
  68. package/dist/components/SeriesBars/components/index.js +1 -0
  69. package/dist/components/SeriesBars/index.d.ts +2 -0
  70. package/dist/components/SeriesBars/index.js +1 -0
  71. package/dist/components/Stat/components/Stat.d.ts +6 -0
  72. package/dist/components/Stat/components/Stat.js +2 -2
  73. package/dist/components/TimelineFeed/components/TimelineFeed.d.ts +37 -0
  74. package/dist/components/TimelineFeed/components/TimelineFeed.js +59 -0
  75. package/dist/components/TimelineFeed/components/index.d.ts +2 -0
  76. package/dist/components/TimelineFeed/components/index.js +1 -0
  77. package/dist/components/TimelineFeed/index.d.ts +2 -0
  78. package/dist/components/TimelineFeed/index.js +1 -0
  79. package/dist/components/TimelinePage/components/TimelinePage.d.ts +35 -0
  80. package/dist/components/TimelinePage/components/TimelinePage.js +77 -0
  81. package/dist/components/TimelinePage/components/index.d.ts +2 -0
  82. package/dist/components/TimelinePage/components/index.js +1 -0
  83. package/dist/components/TimelinePage/index.d.ts +2 -0
  84. package/dist/components/TimelinePage/index.js +1 -0
  85. package/dist/components/VitalsStrip/components/VitalsStrip.d.ts +40 -0
  86. package/dist/components/VitalsStrip/components/VitalsStrip.js +73 -0
  87. package/dist/components/VitalsStrip/components/index.d.ts +2 -0
  88. package/dist/components/VitalsStrip/components/index.js +1 -0
  89. package/dist/components/VitalsStrip/index.d.ts +2 -0
  90. package/dist/components/VitalsStrip/index.js +1 -0
  91. package/dist/components/registry.d.ts +1 -1
  92. package/dist/components/registry.js +254 -2
  93. package/dist/eidotter.css +1 -1
  94. package/dist/index.d.ts +25 -5
  95. package/dist/index.es.js +3897 -1902
  96. package/dist/index.umd.js +3 -3
  97. package/dist/utils/health/formatValue.d.ts +29 -0
  98. package/dist/utils/health/formatValue.js +46 -0
  99. package/dist/utils/health/formatValue.test.d.ts +1 -0
  100. package/dist/utils/health/healthPatterns.test.d.ts +1 -0
  101. package/dist/utils/health/index.d.ts +6 -0
  102. package/dist/utils/health/index.js +3 -0
  103. package/dist/utils/health/labels.d.ts +6 -0
  104. package/dist/utils/health/labels.js +18 -0
  105. package/dist/utils/health/resolveBand.d.ts +12 -0
  106. package/dist/utils/health/resolveBand.js +29 -0
  107. package/dist/utils/health/resolveBand.test.d.ts +1 -0
  108. package/dist/utils/health/types.d.ts +25 -0
  109. package/dist/utils/health/types.js +2 -0
  110. package/dist/utils/index.d.ts +2 -0
  111. package/dist/utils/index.js +1 -0
  112. package/dist/utils/isSafeHref.d.ts +26 -1
  113. package/dist/utils/isSafeHref.js +27 -15
  114. package/dist/utils/isSafeHref.test.d.ts +1 -0
  115. package/dist/utils/isSafeUrl.d.ts +5 -0
  116. package/dist/utils/isSafeUrl.js +5 -0
  117. package/guidelines/README.md +1 -0
  118. package/guidelines/components.md +25 -0
  119. package/guidelines/health-metabolism-patterns.md +212 -0
  120. package/llms.txt +2 -2
  121. package/package.json +2 -2
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { cn } from '../../../utils/cn.js';
4
+ import { Skeleton } from '../../Skeleton/index.js';
5
+ import { formatValue, isKnown, resolveBand, resolveLabels } from '../../../utils/health/index.js';
6
+ const sizeClasses = {
7
+ sm: 'eidotter-vitals-strip--sm gap-1 text-dos-text-xs',
8
+ md: 'eidotter-vitals-strip--md gap-2 text-dos-text-sm',
9
+ lg: 'eidotter-vitals-strip--lg gap-2 text-dos-text-md',
10
+ };
11
+ const valueSizeClasses = {
12
+ sm: 'text-dos-display-xs',
13
+ md: 'text-dos-display-sm',
14
+ lg: 'text-dos-display-md',
15
+ };
16
+ const toneClasses = {
17
+ neutral: 'eidotter-tone--neutral',
18
+ info: 'eidotter-tone--info',
19
+ success: 'eidotter-tone--success',
20
+ warning: 'eidotter-tone--warning',
21
+ critical: 'eidotter-tone--critical',
22
+ };
23
+ const statusClasses = {
24
+ ok: '',
25
+ loading: 'eidotter-vitals-strip__tile--loading',
26
+ empty: 'eidotter-vitals-strip__tile--empty',
27
+ stale: 'eidotter-vitals-strip__tile--stale',
28
+ unknown: 'eidotter-vitals-strip__tile--unknown',
29
+ };
30
+ /** Replacement text for a tile with no value: loading, then offline, then unknown; else null. */
31
+ function tileStateText(flags, labels) {
32
+ if (flags.loading)
33
+ return labels.unknown;
34
+ if (flags.offline)
35
+ return labels.offline;
36
+ if (flags.unknown)
37
+ return labels.unknown;
38
+ return null;
39
+ }
40
+ /**
41
+ * Monitor strip of vital tiles. Tiles are rendered here with Stat's size and
42
+ * text token classes rather than by importing Stat, which is a client
43
+ * component (KTD4). Presentational and hook-free, so it stays server-safe.
44
+ */
45
+ export const VitalsStrip = forwardRef(({ tiles, size = 'md', locale = 'en', labels: labelOverrides, className, ...props }, ref) => {
46
+ const labels = resolveLabels(labelOverrides);
47
+ return (_jsx("div", { ref: ref, className: cn('eidotter-vitals-strip eidotter-health flex flex-wrap w-full font-dos', sizeClasses[size] ?? sizeClasses.md, className), ...props, children: tiles.map((t) => {
48
+ const status = t.status ?? 'ok';
49
+ const loading = status === 'loading';
50
+ const raw = t.pair ? t.pair[0] : t.value;
51
+ const known = t.pair ? isKnown(t.pair[0]) && isKnown(t.pair[1]) : isKnown(t.value);
52
+ const offline = status === 'empty' || (status === 'ok' && !t.pair && t.value === null);
53
+ const unknown = offline || status === 'unknown' || !known;
54
+ const stale = status === 'stale' && !unknown;
55
+ const resolved = resolveBand(unknown ? null : raw, t.bands, t.unit);
56
+ const tone = t.tone ?? resolved.tone;
57
+ const valueText = unknown
58
+ ? null
59
+ : t.pair
60
+ ? formatValue(t.pair[0], { locale, max: t.pair[1] }).text
61
+ : formatValue(t.value, { locale }).text;
62
+ const stateText = tileStateText({ loading, offline, unknown }, labels);
63
+ const name = [
64
+ `${t.label}: ${stateText ?? [valueText, t.unit].filter(Boolean).join(' ')}`,
65
+ stale && labels.stale,
66
+ ]
67
+ .filter(Boolean)
68
+ .join(', ');
69
+ return (_jsxs("div", { role: "group", "aria-label": name, "aria-busy": loading || undefined, className: cn('eidotter-vitals-strip__tile flex flex-col gap-0.5 border border-dos-border-default p-2', toneClasses[tone], statusClasses[status], tone === 'critical' &&
70
+ 'eidotter-vitals-strip__marker border-l-4 border-dos-border-error'), children: [_jsxs("span", { className: "flex items-center gap-1 text-dos-text-brand uppercase tracking-wider leading-tight", children: [tone === 'critical' && (_jsx("span", { "aria-hidden": "true", className: "text-dos-text-error", children: "\u25B2" })), _jsx("span", { className: "truncate", title: t.label, children: t.label })] }), loading ? (_jsx("div", { "aria-hidden": "true", className: "w-full overflow-hidden", children: _jsx(Skeleton, { variant: "figure", lines: 1, animated: false }) })) : (_jsxs("span", { className: cn('eidotter-vitals-strip__value text-dos-text-accent leading-none tabular-nums', valueSizeClasses[size] ?? valueSizeClasses.md), children: [stale ? _jsx("span", { className: "opacity-60", children: valueText }) : (stateText ?? valueText), valueText && t.unit && (_jsx("span", { className: "eidotter-vitals-strip__unit ml-1 text-[0.5em] text-dos-text-brand", children: t.unit }))] })), stale && _jsx("span", { className: "eidotter-vitals-strip__stale", children: labels.stale }), t.secondary && !unknown && (_jsx("span", { className: "eidotter-vitals-strip__secondary text-dos-text-muted leading-tight", children: t.secondary })), resolved.label && (_jsx("span", { className: "eidotter-vitals-strip__band text-dos-text-brand leading-tight", children: resolved.label }))] }, t.id));
71
+ }) }));
72
+ });
73
+ VitalsStrip.displayName = 'VitalsStrip';
@@ -0,0 +1,2 @@
1
+ export { VitalsStrip } from './VitalsStrip.js';
2
+ export type { VitalsStripProps, VitalTile } from './VitalsStrip.js';
@@ -0,0 +1 @@
1
+ export { VitalsStrip } from './VitalsStrip.js';
@@ -0,0 +1,2 @@
1
+ export { VitalsStrip } from './components/index.js';
2
+ export type { VitalsStripProps, VitalTile } from './components/index.js';
@@ -0,0 +1 @@
1
+ export { VitalsStrip } from './components/index.js';
@@ -1,4 +1,4 @@
1
- export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech' | 'tracker';
1
+ export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech' | 'tracker' | 'doctor-portal' | 'dosbts';
2
2
  export interface ProjectInfo {
3
3
  displayName: string;
4
4
  url?: string;
@@ -10,6 +10,8 @@ export const projects = {
10
10
  betamorf: { displayName: 'betamorf' },
11
11
  dmnctech: { displayName: 'dmnc.tech', url: 'https://dmnc.tech' },
12
12
  tracker: { displayName: 'Tracker', url: 'https://tracker.dmnc.tech' },
13
+ 'doctor-portal': { displayName: 'Doctor Portal' },
14
+ dosbts: { displayName: 'DOSBTS' },
13
15
  };
14
16
  // ---------------------------------------------------------------------------
15
17
  // Registry data
@@ -106,6 +108,7 @@ export const componentRegistry = {
106
108
  swiftui: { status: 'planned' },
107
109
  },
108
110
  changelog: [
111
+ { version: '0.39.0', type: 'changed', description: 'Sanitize href via shared isSafeHref — unsafe schemes (javascript:, data:, …) render the label without an anchor' },
109
112
  { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2 — Separator, Stat, Breadcrumb, Progress)' },
110
113
  { version: '0.9.0', type: 'changed', description: 'Fix trail links invisible on dark backgrounds' },
111
114
  { version: '0.3.0', type: 'added', description: 'Initial Breadcrumb — trail array, currentLabel, separator, showBackArrow' },
@@ -350,6 +353,7 @@ export const componentRegistry = {
350
353
  swiftui: { status: 'planned' },
351
354
  },
352
355
  changelog: [
356
+ { version: '0.40.0', type: 'changed', description: 'Add optional unit, note and target slots (additive; existing consumers unchanged)' },
353
357
  { version: '0.19.0', type: 'changed', description: 'Replace hardcoded font-size pixels with V.37 dos tokens' },
354
358
  { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2); deprecate size aliases; add size:sm/lg' },
355
359
  { version: '0.3.0', type: 'added', description: 'Initial Stat — label, value, trend, trendValue, scramble prop' },
@@ -506,6 +510,7 @@ export const componentRegistry = {
506
510
  react: { path: 'src/components/InlineExpand', status: 'canonical' },
507
511
  },
508
512
  changelog: [
513
+ { version: '0.40.0', type: 'changed', description: 'True inline-flow expanded state — marker-pen wrap, trailing collapse control, inline favicon-only sources; conditional render; data-ai-skip on content + legible trigger inside AI-marked prose (DMNC-1316)' },
509
514
  { version: '0.30.0', type: 'changed', description: 'Fix behavioral bugs — double-toggle edge case (DMNC-1063)' },
510
515
  { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first' },
511
516
  { version: '0.8.0', type: 'added', description: 'sources prop — citation links with Google Favicons API fallback' },
@@ -573,8 +578,27 @@ export const componentRegistry = {
573
578
  ChatInput: { origin: 'eidotter', consumers: [], originNote: 'Multiline DOS-styled textarea with Enter-to-send' },
574
579
  ChatContainer: { origin: 'eidotter', consumers: [], originNote: 'Composes ChatHistory + ChatInput into a complete chat interface' },
575
580
  // Layout components
576
- Header: { origin: 'eidotter', consumers: [], originNote: 'Sticky site header composing branding link + Nav — retro/modern variants' },
577
- Footer: { origin: 'eidotter', consumers: [], originNote: 'DOS-themed footer with default legal links (Impressum + Datenschutz) for German compliance' },
581
+ Header: {
582
+ origin: 'eidotter',
583
+ consumers: [],
584
+ originNote: 'Sticky site header composing branding link + Nav — retro/modern variants; optional context row (category badge row + returnTo pill) for cross-site shells (DMNC-1326)',
585
+ variants: {
586
+ 'context:categories': { description: 'Category badge row below the main row — measurable DOM target (stable eidotter-header__categories / __category classes + data-category-key) for the Mark arm-connector (DMNC-1325)', since: '0.39.0' },
587
+ 'context:returnTo': { description: 'Breadcrumb return pill — same-tab via linkComponent by default; reuseTab navigates via window.open(href, "rizomorf-shell") named-tab reuse', since: '0.39.0' },
588
+ },
589
+ changelog: [
590
+ { version: '0.39.0', type: 'added', description: 'Optional `context` prop — category badge row + returnTo breadcrumb pill with reuseTab named-tab navigation (DMNC-1326)' },
591
+ { version: '0.39.0', type: 'changed', description: 'Sanitize brandHref via shared isSafeHref — unsafe schemes fall back to "/" (nav-item links guarded in Nav)' },
592
+ ],
593
+ },
594
+ Footer: {
595
+ origin: 'eidotter',
596
+ consumers: [],
597
+ originNote: 'DOS-themed footer with default legal links (Impressum + Datenschutz) for German compliance',
598
+ changelog: [
599
+ { version: '0.39.0', type: 'changed', description: 'Sanitize link hrefs via shared isSafeHref — unsafe schemes (javascript:, data:, …) render the label without an anchor' },
600
+ ],
601
+ },
578
602
  // Imported from April 2026 design handoff (v0.20.0)
579
603
  InlineLink: {
580
604
  origin: 'rizomorf',
@@ -583,6 +607,7 @@ export const componentRegistry = {
583
607
  originNote: 'In-flow navigational anchor — dotted amber underline, phosphor-invert hover, trailing `▸` / `↗`. Distinct from InlineExpand (destination vs disclosure). Closes rizomorf parity gap #03.',
584
608
  platforms: { react: { path: 'src/components/InlineLink', status: 'canonical' } },
585
609
  changelog: [
610
+ { version: '0.39.0', type: 'changed', description: 'sanitizeHref now delegates to the shared isSafeHref util (extraSchemes tel/ftp/sms); behavior unchanged' },
586
611
  { version: '0.20.0', type: 'added', description: 'Initial InlineLink — internal + external variants, tabnabbing guard when consumer passes target="_blank"' },
587
612
  ],
588
613
  },
@@ -614,6 +639,7 @@ export const componentRegistry = {
614
639
  originNote: '16-segment quantized progress ring (SVG). Powers Tracker’s hold-to-complete feedback ring.',
615
640
  platforms: { react: { path: 'src/components/ProgressRing', status: 'canonical' } },
616
641
  changelog: [
642
+ { version: '0.40.0', type: 'changed', description: 'Add additive decorative prop (aria-hidden, no progressbar role) so Gauge owns meter semantics' },
617
643
  { version: '0.37.0', type: 'added', description: 'Initial ProgressRing — discrete quantized segment arcs, phosphor glow, centre label, currentColor theming' },
618
644
  ],
619
645
  },
@@ -637,6 +663,232 @@ export const componentRegistry = {
637
663
  { version: '0.37.0', type: 'added', description: 'Initial HoldToComplete — rAF hold animation with accelerating ease, onHoldProgress hook, Enter/Space to complete' },
638
664
  ],
639
665
  },
666
+ Mark: {
667
+ origin: 'rizomorf',
668
+ consumers: ['rizomorf'],
669
+ since: '0.39.0',
670
+ originNote: 'Morphing organic avatar mark (a.k.a. RizomorfAvatar) with runtime-measured quadratic-bezier arm connectors to Header nav badges (DMNC-1325; Header badge row: DMNC-1326).',
671
+ variants: {
672
+ 'morph:true': { description: 'Border-radius morph loop (~6s ease-in-out), disabled under prefers-reduced-motion', since: '0.39.0' },
673
+ 'morph:false': { description: 'Static organic silhouette', since: '0.39.0' },
674
+ },
675
+ platforms: { react: { path: 'src/components/Brand', status: 'canonical' } },
676
+ changelog: [
677
+ { version: '0.39.0', type: 'added', description: 'Initial Mark — soft-radial egg-yolk fill + CSS border-radius morph + SVG arm connectors with alternating control-point offsets' },
678
+ ],
679
+ },
680
+ Connector: {
681
+ origin: 'eidotter',
682
+ consumers: [],
683
+ since: '0.39.0',
684
+ originNote: 'Organic tapered-ribbon SVG overlay (DMNC-1388) — filled variable-width paths from a source node (the Mark) to N anchors, amber→category gradient, stop-at-edge attach, ResizeObserver/MutationObserver re-flow + rAF idle sway (reduced-motion safe). Decorative/aria-hidden; content lives in the DOM underneath.',
685
+ variants: {
686
+ 'weight:delicate': { description: 'Thin near-straight product-chrome thread (default)', since: '0.39.0' },
687
+ 'weight:medium': { description: 'Bolder ribbon for large surfaces', since: '0.39.0' },
688
+ },
689
+ platforms: { react: { path: 'src/components/Connector', status: 'canonical' } },
690
+ changelog: [
691
+ { version: '0.39.0', type: 'added', description: 'Initial Connector — tapered filled-path ribbons, per-target amber→category gradient, attach-at-edge geometry, idle sway' },
692
+ ],
693
+ },
694
+ TimelineFeed: {
695
+ origin: 'eidotter',
696
+ consumers: [],
697
+ since: '0.39.0',
698
+ originNote: 'Dense product-grade feed (the "J" layout) — category dot · date · title · outlined kind-tag rows threaded by a thin amber spine, with a category chip row that culls the list. Shares the TimelineEntryData model with TimelineContainer.',
699
+ platforms: { react: { path: 'src/components/TimelineFeed', status: 'canonical' } },
700
+ changelog: [
701
+ { version: '0.39.0', type: 'added', description: 'Initial TimelineFeed — dense category-dotted rows, amber-mono, controllable category filter' },
702
+ ],
703
+ },
704
+ TimelinePage: {
705
+ origin: 'eidotter',
706
+ consumers: [],
707
+ since: '0.39.0',
708
+ originNote: 'Bret-Victor-style blog timeline (the "K2" layout) — year-grouped, amber-only spine + dots, neutral dates, big titles + description, right-hand hover preview panel, optional Mark brand slot + category filter. Shares the TimelineEntryData model with TimelineContainer.',
709
+ platforms: { react: { path: 'src/components/TimelinePage', status: 'canonical' } },
710
+ changelog: [
711
+ { version: '0.39.0', type: 'added', description: 'Initial TimelinePage — grouped blog timeline with hover-preview panel and category filter' },
712
+ ],
713
+ },
714
+ Gauge: {
715
+ origin: 'eidotter',
716
+ consumers: ['doctor-portal', 'dosbts'],
717
+ since: '0.40.0',
718
+ originNote: 'Ring gauge for one metabolic value. Carries the SCUM metabolism-screen dial pattern (value, target, provenance) as used in diabetes apps. Intended consumers: doctor-portal, dosbts (not yet adopted). Figma: none yet.',
719
+ variants: {
720
+ 'size:sm': { description: 'Compact gauge', since: '0.40.0' },
721
+ 'size:md': { description: 'Standard gauge', since: '0.40.0' },
722
+ 'size:lg': { description: 'Hero gauge', since: '0.40.0' },
723
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
724
+ 'tone:info': { description: 'Informational reading (solid fill)', since: '0.40.0' },
725
+ 'tone:success': { description: 'In-target reading (dither fill)', since: '0.40.0' },
726
+ 'tone:warning': { description: 'Needs attention (hatch fill)', since: '0.40.0' },
727
+ 'tone:critical': { description: 'Out-of-range reading (dotted fill)', since: '0.40.0' },
728
+ 'status:ok': { description: 'Live value rendered normally', since: '0.40.0' },
729
+ 'status:loading': { description: 'Value is being fetched', since: '0.40.0' },
730
+ 'status:empty': { description: 'No data available', since: '0.40.0' },
731
+ 'status:stale': { description: 'Value is older than expected', since: '0.40.0' },
732
+ 'status:unknown': { description: 'Value could not be determined', since: '0.40.0' },
733
+ },
734
+ platforms: {
735
+ react: { path: 'src/components/Gauge', status: 'canonical' },
736
+ swiftui: { status: 'planned' },
737
+ },
738
+ changelog: [
739
+ { version: '0.40.0', type: 'added', description: 'Initial Gauge (health metabolism family)' },
740
+ ],
741
+ },
742
+ Meter: {
743
+ origin: 'eidotter',
744
+ consumers: ['dosbts'],
745
+ since: '0.40.0',
746
+ originNote: 'Single labelled meter bar with tick and tone/pattern pairing. Carries the SCUM stat-bar pattern (food, water, energy) for diabetes values such as carbs on board. Intended consumer: dosbts (not yet adopted). Figma: none yet.',
747
+ variants: {
748
+ 'orientation:vertical': { description: 'Vertical bar with label beneath', since: '0.40.0' },
749
+ 'orientation:horizontal': { description: 'Horizontal bar with label alongside', since: '0.40.0' },
750
+ 'size:xs': { description: 'Extra-compact meter', since: '0.40.0' },
751
+ 'size:sm': { description: 'Compact meter', since: '0.40.0' },
752
+ 'size:md': { description: 'Standard meter', since: '0.40.0' },
753
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
754
+ 'tone:info': { description: 'Informational reading (solid fill)', since: '0.40.0' },
755
+ 'tone:success': { description: 'In-target reading (dither fill)', since: '0.40.0' },
756
+ 'tone:warning': { description: 'Needs attention (hatch fill)', since: '0.40.0' },
757
+ 'tone:critical': { description: 'Out-of-range reading (dotted fill)', since: '0.40.0' },
758
+ 'pattern:solid': { description: 'Solid fill', since: '0.40.0' },
759
+ 'pattern:dither': { description: 'Dither fill', since: '0.40.0' },
760
+ 'pattern:hatch': { description: 'Hatch fill', since: '0.40.0' },
761
+ 'pattern:dotted': { description: 'Dotted fill', since: '0.40.0' },
762
+ },
763
+ platforms: {
764
+ react: { path: 'src/components/Meter', status: 'canonical' },
765
+ swiftui: { status: 'planned' },
766
+ },
767
+ changelog: [
768
+ { version: '0.40.0', type: 'added', description: 'Initial Meter (health metabolism family)' },
769
+ ],
770
+ },
771
+ MeterGroup: {
772
+ origin: 'eidotter',
773
+ consumers: ['dosbts'],
774
+ since: '0.40.0',
775
+ originNote: 'Layout wrapper for several Meters. Carries the SCUM metabolism-screen stat cluster pattern. Intended consumer: dosbts (not yet adopted). Figma: none yet.',
776
+ variants: {
777
+ 'layout:row': { description: 'Meters in a single row', since: '0.40.0' },
778
+ 'layout:grid': { description: 'Meters in a wrapping grid', since: '0.40.0' },
779
+ },
780
+ platforms: {
781
+ react: { path: 'src/components/Meter', status: 'canonical' },
782
+ swiftui: { status: 'planned' },
783
+ },
784
+ changelog: [
785
+ { version: '0.40.0', type: 'added', description: 'Initial MeterGroup (health metabolism family)' },
786
+ ],
787
+ },
788
+ RangeBar: {
789
+ origin: 'eidotter',
790
+ consumers: ['doctor-portal', 'dosbts'],
791
+ since: '0.40.0',
792
+ originNote: 'Banded range bar showing where a value sits against target segments, as in diabetes time-in-range displays. Carries the SCUM condition-bar pattern. Intended consumers: doctor-portal, dosbts (not yet adopted). Figma: none yet.',
793
+ variants: {
794
+ 'orientation:vertical': { description: 'Vertical track', since: '0.40.0' },
795
+ 'orientation:horizontal': { description: 'Horizontal track', since: '0.40.0' },
796
+ 'size:sm': { description: 'Compact track', since: '0.40.0' },
797
+ 'size:md': { description: 'Standard track', since: '0.40.0' },
798
+ 'size:lg': { description: 'Large track', since: '0.40.0' },
799
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
800
+ 'tone:info': { description: 'Informational reading (solid fill)', since: '0.40.0' },
801
+ 'tone:success': { description: 'In-target reading (dither fill)', since: '0.40.0' },
802
+ 'tone:warning': { description: 'Needs attention (hatch fill)', since: '0.40.0' },
803
+ 'tone:critical': { description: 'Out-of-range reading (dotted fill)', since: '0.40.0' },
804
+ 'pattern:none': { description: 'No fill pattern', since: '0.40.0' },
805
+ 'pattern:solid': { description: 'Solid fill', since: '0.40.0' },
806
+ 'pattern:dither': { description: 'Dither fill', since: '0.40.0' },
807
+ 'pattern:hatch': { description: 'Hatch fill', since: '0.40.0' },
808
+ 'pattern:dotted': { description: 'Dotted fill', since: '0.40.0' },
809
+ },
810
+ platforms: {
811
+ react: { path: 'src/components/RangeBar', status: 'canonical' },
812
+ swiftui: { status: 'planned' },
813
+ },
814
+ changelog: [
815
+ { version: '0.40.0', type: 'added', description: 'Initial RangeBar (health metabolism family)' },
816
+ ],
817
+ },
818
+ SeriesBars: {
819
+ origin: 'eidotter',
820
+ consumers: ['doctor-portal'],
821
+ since: '0.40.0',
822
+ originNote: 'Bar series for per-period values such as daily carbs or time-in-range, as in diabetes reports. Carries the SCUM history-chart pattern. Intended consumer: doctor-portal (not yet adopted). Figma: none yet.',
823
+ variants: {
824
+ 'size:sm': { description: 'Short plot', since: '0.40.0' },
825
+ 'size:md': { description: 'Standard plot', since: '0.40.0' },
826
+ 'size:lg': { description: 'Tall plot', since: '0.40.0' },
827
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
828
+ 'tone:info': { description: 'Informational reading (solid fill)', since: '0.40.0' },
829
+ 'tone:success': { description: 'In-target reading (dither fill)', since: '0.40.0' },
830
+ 'tone:warning': { description: 'Needs attention (hatch fill)', since: '0.40.0' },
831
+ 'tone:critical': { description: 'Out-of-range reading (dotted fill)', since: '0.40.0' },
832
+ },
833
+ platforms: {
834
+ react: { path: 'src/components/SeriesBars', status: 'canonical' },
835
+ swiftui: { status: 'planned' },
836
+ },
837
+ changelog: [
838
+ { version: '0.40.0', type: 'added', description: 'Initial SeriesBars (health metabolism family)' },
839
+ ],
840
+ },
841
+ DecayStrip: {
842
+ origin: 'eidotter',
843
+ consumers: ['dosbts'],
844
+ since: '0.40.0',
845
+ originNote: 'Stepped block strip of consumer-supplied remaining-amount samples with a now marker (active-on-board curve in diabetes apps). Carries the SCUM timed-effect pattern; no absorption math. Intended consumer: dosbts (not yet adopted). Figma: none yet.',
846
+ variants: {
847
+ 'size:sm': { description: 'Compact strip', since: '0.40.0' },
848
+ 'size:md': { description: 'Standard strip', since: '0.40.0' },
849
+ 'size:lg': { description: 'Large strip', since: '0.40.0' },
850
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
851
+ 'tone:info': { description: 'Informational reading', since: '0.40.0' },
852
+ 'tone:success': { description: 'In-target reading', since: '0.40.0' },
853
+ 'tone:warning': { description: 'Needs attention', since: '0.40.0' },
854
+ 'tone:critical': { description: 'Out-of-range reading', since: '0.40.0' },
855
+ },
856
+ platforms: {
857
+ react: { path: 'src/components/DecayStrip', status: 'canonical' },
858
+ swiftui: { status: 'planned' },
859
+ },
860
+ changelog: [
861
+ { version: '0.40.0', type: 'added', description: 'Initial DecayStrip (health metabolism family)' },
862
+ ],
863
+ },
864
+ VitalsStrip: {
865
+ origin: 'eidotter',
866
+ consumers: ['doctor-portal', 'dosbts'],
867
+ since: '0.40.0',
868
+ originNote: 'Server-safe row of vitals tiles with value, unit, secondary text and bands, and status. Carries the SCUM vitals header pattern for diabetes summaries. Intended consumers: doctor-portal, dosbts (not yet adopted). Figma: none yet.',
869
+ variants: {
870
+ 'size:sm': { description: 'Compact tiles', since: '0.40.0' },
871
+ 'size:md': { description: 'Standard tiles', since: '0.40.0' },
872
+ 'size:lg': { description: 'Large tiles', since: '0.40.0' },
873
+ 'tone:neutral': { description: 'Unfilled/neutral reading with no judgement', since: '0.40.0' },
874
+ 'tone:info': { description: 'Informational reading', since: '0.40.0' },
875
+ 'tone:success': { description: 'In-target reading', since: '0.40.0' },
876
+ 'tone:warning': { description: 'Needs attention', since: '0.40.0' },
877
+ 'tone:critical': { description: 'Out-of-range reading', since: '0.40.0' },
878
+ 'status:ok': { description: 'Live value rendered normally', since: '0.40.0' },
879
+ 'status:loading': { description: 'Value is being fetched', since: '0.40.0' },
880
+ 'status:empty': { description: 'No data available', since: '0.40.0' },
881
+ 'status:stale': { description: 'Value is older than expected', since: '0.40.0' },
882
+ 'status:unknown': { description: 'Value could not be determined', since: '0.40.0' },
883
+ },
884
+ platforms: {
885
+ react: { path: 'src/components/VitalsStrip', status: 'canonical' },
886
+ swiftui: { status: 'planned' },
887
+ },
888
+ changelog: [
889
+ { version: '0.40.0', type: 'added', description: 'Initial VitalsStrip (health metabolism family)' },
890
+ ],
891
+ },
640
892
  };
641
893
  // ---------------------------------------------------------------------------
642
894
  // Utility functions