eidotter 0.37.1 → 0.37.3

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 (45) hide show
  1. package/LICENSE.md +3 -3
  2. package/dist/components/Brand/components/Wordmark.d.ts +1 -1
  3. package/dist/components/Brand/components/Wordmark.js +1 -1
  4. package/dist/components/CalendarHeatmap/components/CalendarHeatmap.d.ts +40 -0
  5. package/dist/components/CalendarHeatmap/components/CalendarHeatmap.js +84 -0
  6. package/dist/components/CalendarHeatmap/components/index.d.ts +2 -0
  7. package/dist/components/CalendarHeatmap/components/index.js +1 -0
  8. package/dist/components/CalendarHeatmap/index.d.ts +2 -0
  9. package/dist/components/CalendarHeatmap/index.js +1 -0
  10. package/dist/components/CommandPrompt/components/CommandPrompt.js +1 -1
  11. package/dist/components/HoldToComplete/components/HoldToComplete.d.ts +30 -0
  12. package/dist/components/HoldToComplete/components/HoldToComplete.js +145 -0
  13. package/dist/components/HoldToComplete/components/index.d.ts +2 -0
  14. package/dist/components/HoldToComplete/components/index.js +1 -0
  15. package/dist/components/HoldToComplete/index.d.ts +2 -0
  16. package/dist/components/HoldToComplete/index.js +1 -0
  17. package/dist/components/InlineExpand/components/InlineExpand.d.ts +1 -1
  18. package/dist/components/InlineExpand/components/InlineExpand.js +30 -2
  19. package/dist/components/ProgressRing/components/ProgressRing.d.ts +35 -0
  20. package/dist/components/ProgressRing/components/ProgressRing.js +34 -0
  21. package/dist/components/ProgressRing/components/index.d.ts +2 -0
  22. package/dist/components/ProgressRing/components/index.js +1 -0
  23. package/dist/components/ProgressRing/index.d.ts +2 -0
  24. package/dist/components/ProgressRing/index.js +1 -0
  25. package/dist/components/RetroEffects/components/RetroEffects.d.ts +28 -5
  26. package/dist/components/RetroEffects/components/RetroEffects.js +24 -8
  27. package/dist/components/TimelineContainer/components/TimelineContainer.js +1 -1
  28. package/dist/components/registry.d.ts +1 -1
  29. package/dist/components/registry.js +422 -16
  30. package/dist/eidotter.css +1 -1
  31. package/dist/index.d.ts +6 -0
  32. package/dist/index.es.js +1791 -505
  33. package/dist/index.umd.js +3 -3
  34. package/package.json +12 -4
  35. package/src/styles/dos-utilities.css +15 -6
  36. package/src/styles/fonts/JetBrainsMonoNerdFont-Bold.woff2 +0 -0
  37. package/src/styles/fonts/JetBrainsMonoNerdFont-Medium.woff2 +0 -0
  38. package/src/styles/fonts/JetBrainsMonoNerdFont-Regular.woff2 +0 -0
  39. package/src/styles/fonts/JetBrainsMonoNerdFont-SemiBold.woff2 +0 -0
  40. package/src/styles/fonts/JetBrainsMonoNerdFont_OFL.txt +93 -0
  41. package/src/styles/fonts.css +50 -3
  42. package/src/styles/tokens.css +9 -4
  43. package/tailwind.preset.cjs +9 -1
  44. package/src/styles/fonts/Flexi_IBM_VGA_LICENSE.txt +0 -428
  45. package/src/styles/fonts/Flexi_IBM_VGA_True.ttf +0 -0
@@ -42,11 +42,18 @@ const markBootedThisSession = (key) => {
42
42
  export const RetroEffects = ({ scanlines = true, glow = true, flicker = true, bloom = false, powered = true, boot = false, bootOnce = false, bootStorageKey = DEFAULT_BOOT_STORAGE_KEY, intensity = 1, className, onPowerStateChange, onPowerOn, onPowerOff, onBootComplete, }) => {
43
43
  const prevPoweredRef = useRef(powered);
44
44
  const [powerState, setPowerState] = useState(powered ? 'on' : 'off');
45
- // Non-gated boot keeps its on-mount behavior; the gated (bootOnce) path starts
46
- // un-booted and is decided post-mount so SSR/first paint never render a stale
47
- // overlay (no hydration mismatch on reload when the flag is already set).
48
- // (Non-gated boot is intentionally NOT SSR-gated — it plays on every mount.)
49
- const [booting, setBooting] = useState(boot && !bootOnce);
45
+ // The opaque boot cover renders on first paint for BOTH the gated and
46
+ // non-gated paths whenever `boot` is set. This is the no-FOUC fix (DMNC-1184):
47
+ // the full-viewport panels are present in the server HTML, so the page is
48
+ // never visible before the turn-on — the animation is the reveal. `booting`
49
+ // mirrors the server value on the client's first render (no sessionStorage
50
+ // read here), so there is no hydration mismatch. The gated (bootOnce) decision
51
+ // is made post-mount: if this tab already booted, the cover is torn down by
52
+ // the decision effect once it runs. The server can't read sessionStorage, so a
53
+ // returning visitor's full reload paints the cover and suppresses it on
54
+ // hydration (a brief blackout, not a bare-content flash) — see the bootOnce
55
+ // JSDoc for the SSR trade.
56
+ const [booting, setBooting] = useState(boot);
50
57
  // Boot completes exactly once per mount. Guards against the StrictMode dev
51
58
  // double-invoke of effects and the (theoretical) animationend + safety-timeout
52
59
  // race both calling through twice.
@@ -85,12 +92,16 @@ export const RetroEffects = ({ scanlines = true, glow = true, flicker = true, bl
85
92
  if (!boot || !bootOnce)
86
93
  return;
87
94
  if (hasBootedThisSession(bootStorageKeyRef.current)) {
88
- // Already shown this session — skip, but still signal completion so
89
- // consumers sequencing boot text off onBootComplete don't stall.
95
+ // Already shown this session — tear down the SSR cover (it was rendered to
96
+ // prevent the first-paint flash, DMNC-1184) and signal completion so
97
+ // consumers sequencing boot text off onBootComplete don't stall. No replay.
98
+ setBooting(false);
90
99
  signalBootComplete();
91
100
  return;
92
101
  }
93
- setBooting(true);
102
+ // First visit this tab — keep the cover up; it plays and records the session
103
+ // flag on completion (`completeBoot`). `booting` is already true from the
104
+ // initial state, so there is nothing to set here.
94
105
  // Mount-only: gating is a first-load decision, not reactive to prop churn.
95
106
  // eslint-disable-next-line react-hooks/exhaustive-deps
96
107
  }, []);
@@ -100,6 +111,11 @@ export const RetroEffects = ({ scanlines = true, glow = true, flicker = true, bl
100
111
  useEffect(() => {
101
112
  if (!booting)
102
113
  return;
114
+ // Already settled (e.g. the gated already-booted branch tore the SSR cover
115
+ // down and signalled in the decision effect, which runs first): don't arm a
116
+ // safety timer that we'd immediately clear on the next render.
117
+ if (bootSignaledRef.current)
118
+ return;
103
119
  if (prefersReducedMotion()) {
104
120
  completeBoot();
105
121
  return;
@@ -197,7 +197,7 @@ export const TimelineContainer = ({ entries, mode = 'interactive', zoomLevel: co
197
197
  return iso;
198
198
  }
199
199
  };
200
- return (_jsxs("div", { ref: containerRef, ...props, className: cn('font-dos text-dos-text-brand p-4 min-h-[200px]', 'eidotter-timeline-container', isStatic && 'eidotter-timeline-container--static', isFeed && 'eidotter-timeline-container--feed', isMasterDetail && 'eidotter-timeline-container--master-detail', props.className), role: "region", "aria-label": props['aria-label'] ?? 'Timeline', tabIndex: isVerticalFeed || isMasterDetail ? undefined : 0, children: [_jsx("div", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement }), !isVerticalFeed && !isMasterDetail && (_jsx(ZoomControls, { zoomLevel: zoomLevel, canZoomIn: canZoomIn, canZoomOut: canZoomOut, onZoomIn: handleZoomIn, onZoomOut: drillUp, onReset: reset, breadcrumbs: isDrillDownEnabled ? breadcrumbs : [], onBreadcrumbClick: (index) => {
200
+ return (_jsxs("div", { ref: containerRef, ...props, className: cn('font-dos text-dos-text-brand p-4', 'eidotter-timeline-container', isStatic && 'eidotter-timeline-container--static', isFeed && 'eidotter-timeline-container--feed', isMasterDetail && 'eidotter-timeline-container--master-detail', props.className), role: "region", "aria-label": props['aria-label'] ?? 'Timeline', tabIndex: isVerticalFeed || isMasterDetail ? undefined : 0, children: [_jsx("div", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement }), !isVerticalFeed && !isMasterDetail && (_jsx(ZoomControls, { zoomLevel: zoomLevel, canZoomIn: canZoomIn, canZoomOut: canZoomOut, onZoomIn: handleZoomIn, onZoomOut: drillUp, onReset: reset, breadcrumbs: isDrillDownEnabled ? breadcrumbs : [], onBreadcrumbClick: (index) => {
201
201
  // Navigate to a specific breadcrumb level
202
202
  const stepsBack = breadcrumbs.length - 1 - index;
203
203
  for (let i = 0; i < stepsBack; i++) {
@@ -1,4 +1,4 @@
1
- export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech';
1
+ export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech' | 'tracker';
2
2
  export interface ProjectInfo {
3
3
  displayName: string;
4
4
  url?: string;
@@ -9,6 +9,7 @@ export const projects = {
9
9
  lifelines: { displayName: 'Lifelines' },
10
10
  betamorf: { displayName: 'betamorf' },
11
11
  dmnctech: { displayName: 'dmnc.tech', url: 'https://dmnc.tech' },
12
+ tracker: { displayName: 'Tracker', url: 'https://tracker.dmnc.tech' },
12
13
  };
13
14
  // ---------------------------------------------------------------------------
14
15
  // Registry data
@@ -41,10 +42,34 @@ export const componentRegistry = {
41
42
  Accordion: {
42
43
  origin: 'eidotter',
43
44
  consumers: ['rizomorf'],
45
+ since: '0.2.0',
46
+ originNote: 'Collapsible section with CSS grid expand/collapse — always-in-DOM, transitions grid-template-rows',
47
+ platforms: {
48
+ react: { path: 'src/components/Accordion', status: 'canonical' },
49
+ swiftui: { status: 'planned' },
50
+ },
51
+ changelog: [
52
+ { version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes for accordion trigger' },
53
+ { version: '0.19.0', type: 'changed', description: 'Remove 500px expand cap — animate grid-template-rows to natural height' },
54
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 3 — Accordion, Footer, Tabs, FilterBar)' },
55
+ { version: '0.9.0', type: 'changed', description: 'Replace text-primary with cga-amber for theme consistency' },
56
+ { version: '0.8.0', type: 'changed', description: 'Design-craft animation polish with phosphor enter effects' },
57
+ { version: '0.2.0', type: 'added', description: 'Initial Accordion (Section) — collapsible section, inert attribute, reduced-motion safe' },
58
+ ],
44
59
  },
45
60
  AccordionFill: {
46
61
  origin: 'eidotter',
47
62
  consumers: ['rizomorf'],
63
+ since: '0.4.0',
64
+ originNote: 'Convenience wrapper around Section — renders a list of sections from a data array',
65
+ platforms: {
66
+ react: { path: 'src/components/Accordion', status: 'canonical' },
67
+ swiftui: { status: 'planned' },
68
+ },
69
+ changelog: [
70
+ { version: '0.16.0', type: 'changed', description: 'Tailwind-first pass alongside Accordion migration' },
71
+ { version: '0.4.0', type: 'added', description: 'Initial AccordionFill — array-driven Section list' },
72
+ ],
48
73
  },
49
74
  Badge: {
50
75
  origin: 'eidotter',
@@ -74,6 +99,17 @@ export const componentRegistry = {
74
99
  Breadcrumb: {
75
100
  origin: 'rizomorf',
76
101
  consumers: ['rizomorf'],
102
+ since: '0.3.0',
103
+ originNote: 'Trail navigation with configurable separator and optional back-arrow',
104
+ platforms: {
105
+ react: { path: 'src/components/Breadcrumb', status: 'canonical' },
106
+ swiftui: { status: 'planned' },
107
+ },
108
+ changelog: [
109
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2 — Separator, Stat, Breadcrumb, Progress)' },
110
+ { version: '0.9.0', type: 'changed', description: 'Fix trail links invisible on dark backgrounds' },
111
+ { version: '0.3.0', type: 'added', description: 'Initial Breadcrumb — trail array, currentLabel, separator, showBackArrow' },
112
+ ],
77
113
  },
78
114
  Button: {
79
115
  origin: 'eidotter',
@@ -129,18 +165,215 @@ export const componentRegistry = {
129
165
  { version: '0.3.0', type: 'added', description: 'Initial Card with default/elevated/bordered variants' },
130
166
  ],
131
167
  },
132
- Checkbox: { origin: 'eidotter', consumers: ['steuerdash'] },
133
- CommandPrompt: { origin: 'eidotter', consumers: ['rizomorf'] },
134
- Icon: { origin: 'eidotter', consumers: ['rizomorf'] },
135
- Input: { origin: 'eidotter', consumers: ['steuerdash'] },
168
+ Checkbox: {
169
+ origin: 'eidotter',
170
+ consumers: ['steuerdash'],
171
+ since: '0.3.0',
172
+ variants: {
173
+ 'size:sm': { description: 'Compact 16px checkbox for dense forms', since: '0.16.0' },
174
+ 'size:md': { description: 'Standard 20px checkbox', since: '0.3.0', usedBy: ['steuerdash'] },
175
+ },
176
+ platforms: {
177
+ react: { path: 'src/components/Checkbox', status: 'canonical' },
178
+ swiftui: { status: 'planned' },
179
+ },
180
+ changelog: [
181
+ { version: '0.30.0', type: 'changed', description: 'A11y: remediate Phase 1 audit defects + high-contrast glow gaps' },
182
+ { version: '0.19.0', type: 'changed', description: 'Replace hardcoded font-size pixels with V.37 dos tokens' },
183
+ { version: '0.16.0', type: 'changed', description: 'Migrate to React Aria + Tailwind-first (Wave 1); add size:sm variant' },
184
+ { version: '0.3.0', type: 'added', description: 'Initial Checkbox — controlled/uncontrolled, indeterminate; BEM CSS implementation' },
185
+ ],
186
+ },
187
+ CommandPrompt: {
188
+ origin: 'eidotter',
189
+ consumers: ['rizomorf'],
190
+ since: '0.3.0',
191
+ originNote: 'DOS command-line input — blinking cursor, configurable prompt string, onCommand callback',
192
+ platforms: {
193
+ react: { path: 'src/components/CommandPrompt', status: 'canonical' },
194
+ },
195
+ changelog: [
196
+ { version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes' },
197
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
198
+ { version: '0.3.0', type: 'added', description: 'Initial CommandPrompt — prompt prop, onCommand, autoFocus, disabled' },
199
+ ],
200
+ },
201
+ Icon: {
202
+ origin: 'eidotter',
203
+ consumers: ['rizomorf'],
204
+ since: '0.13.0',
205
+ originNote: 'Thin wrapper around pixelarticons (MIT) + custom PixelX for Close — 12 public names, ICON_MAP',
206
+ variants: {
207
+ 'name:Info': { description: 'Informational circle-i', since: '0.13.0', usedBy: ['rizomorf'] },
208
+ 'name:Warning': { description: 'Warning triangle', since: '0.13.0', usedBy: ['rizomorf'] },
209
+ 'name:Error': { description: 'Error octagon', since: '0.13.0' },
210
+ 'name:Done': { description: 'Checkmark (shares Check glyph)', since: '0.13.0' },
211
+ 'name:Check': { description: 'Checkmark', since: '0.13.0' },
212
+ 'name:Close': { description: 'Custom pixel-art X mark (no pixelarticons glyph)', since: '0.19.0', usedBy: ['rizomorf'] },
213
+ 'name:Chevron Up': { description: 'Up caret', since: '0.13.0' },
214
+ 'name:Chevron Down': { description: 'Down caret', since: '0.13.0' },
215
+ 'name:App': { description: 'Grid/app launcher', since: '0.13.0' },
216
+ 'name:Cancel': { description: 'Minus glyph — window minimize control', since: '0.13.0' },
217
+ 'name:Fullscreen': { description: 'Expand to fullscreen', since: '0.13.0' },
218
+ 'name:Add': { description: 'Plus / add action', since: '0.13.0' },
219
+ 'size:L': { description: 'Large — 56 × 56 px', since: '0.13.0', usedBy: ['rizomorf'] },
220
+ 'size:S': { description: 'Small — 24 × 24 px', since: '0.13.0' },
221
+ },
222
+ platforms: {
223
+ react: { path: 'src/components/Icon', status: 'canonical' },
224
+ },
225
+ changelog: [
226
+ { version: '0.30.0', type: 'changed', description: 'A11y: fix aria-prohibited-attr on Icon (Phase 1 audit defect)' },
227
+ { version: '0.30.0', type: 'changed', description: 'Icon inherits color from context instead of hardcoding amber' },
228
+ { version: '0.19.0', type: 'added', description: 'Custom pixel-art X mark for Close (pixelarticons v2 has no standalone X glyph)' },
229
+ { version: '0.18.0', type: 'changed', description: 'Swap @untitledui-pro/icons for pixelarticons (MIT) — license compliance' },
230
+ { version: '0.13.0', type: 'added', description: 'Initial Icon component — ICON_MAP with 12 public names, size L/S' },
231
+ ],
232
+ },
233
+ Input: {
234
+ origin: 'eidotter',
235
+ consumers: ['steuerdash'],
236
+ since: '0.3.0',
237
+ variants: {
238
+ 'variant:default': { description: 'Standard text input with amber focus border', since: '0.3.0', usedBy: ['steuerdash'] },
239
+ 'variant:error': { description: 'Error state — red border, errorMessage slot', since: '0.3.0', usedBy: ['steuerdash'] },
240
+ },
241
+ platforms: {
242
+ react: { path: 'src/components/Input', status: 'canonical' },
243
+ swiftui: { status: 'planned' },
244
+ },
245
+ changelog: [
246
+ { version: '0.17.0', type: 'changed', description: 'Migrate to React Aria TextField (Wave 5)' },
247
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 4 — Input, Modal, Nav)' },
248
+ { version: '0.9.0', type: 'changed', description: 'Replace text-primary with cga-amber' },
249
+ { version: '0.3.0', type: 'added', description: 'Initial Input — label, description, errorMessage, isRequired' },
250
+ ],
251
+ },
136
252
  Lightbox: { origin: 'eidotter', consumers: ['dmnctech'], since: '0.22.0', originNote: 'Fullscreen image viewer extending Modal pattern. Keyboard nav, prev/next, counter, swipe.' },
137
- Modal: { origin: 'eidotter', consumers: ['pomodoke-calendar'] },
138
- Progress: { origin: 'eidotter', consumers: ['steuerdash'], since: '0.3.0' },
139
- RetroEffects: { origin: 'spacewar', consumers: ['spacewar', 'rizomorf'], originNote: 'CRT scanline/glow effects from Spacewar!' },
253
+ Modal: {
254
+ origin: 'eidotter',
255
+ consumers: ['pomodoke-calendar'],
256
+ since: '0.3.0',
257
+ originNote: 'DOS-styled dialog built on React Aria ModalOverlay/Modal/Dialog — focus trap, Esc to close, backdrop',
258
+ platforms: {
259
+ react: { path: 'src/components/Modal', status: 'canonical' },
260
+ swiftui: { status: 'planned' },
261
+ },
262
+ changelog: [
263
+ { version: '0.18.0', type: 'changed', description: 'Normalize event handlers; replace raw <button> with AriaButton for close control' },
264
+ { version: '0.17.0', type: 'changed', description: 'Migrate to React Aria ModalOverlay/Modal/Dialog (Wave 5)' },
265
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 4 — Input, Modal, Nav)' },
266
+ { version: '0.3.0', type: 'added', description: 'Initial Modal — isOpen, onOpenChange, title, footer slot' },
267
+ ],
268
+ },
269
+ Progress: {
270
+ origin: 'eidotter',
271
+ consumers: ['steuerdash'],
272
+ since: '0.3.0',
273
+ variants: {
274
+ 'variant:default': { description: 'Amber progress bar', since: '0.3.0', usedBy: ['steuerdash'] },
275
+ 'variant:success': { description: 'Green completion indicator', since: '0.3.0' },
276
+ 'variant:warning': { description: 'Amber warning progress', since: '0.3.0' },
277
+ 'variant:error': { description: 'Red error/failed progress', since: '0.3.0' },
278
+ 'size:sm': { description: 'Compact single-row bar', since: '0.16.0' },
279
+ 'size:md': { description: 'Standard bar height', since: '0.3.0', usedBy: ['steuerdash'] },
280
+ 'size:lg': { description: 'Tall bar for prominent display', since: '0.16.0' },
281
+ 'trackStyle:block': { description: 'Solid block fill (default)', since: '0.3.0', usedBy: ['steuerdash'] },
282
+ 'trackStyle:bordered': { description: 'Outline bar with fill inside', since: '0.3.0' },
283
+ 'trackStyle:gradient': { description: 'Gradient fill for smooth visual', since: '0.3.0' },
284
+ },
285
+ platforms: {
286
+ react: { path: 'src/components/Progress', status: 'canonical' },
287
+ swiftui: { status: 'planned' },
288
+ },
289
+ changelog: [
290
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2); deprecate size aliases small/medium/large → sm/md/lg' },
291
+ { version: '0.3.0', type: 'added', description: 'Initial Progress — blocks character cells, indeterminate, showLabel, glow prop' },
292
+ ],
293
+ },
294
+ RetroEffects: {
295
+ origin: 'spacewar',
296
+ consumers: ['spacewar', 'rizomorf'],
297
+ since: '0.3.0',
298
+ originNote: 'CRT scanline/glow effects from Spacewar! — compositor-only, prefers-reduced-motion safe',
299
+ platforms: {
300
+ react: { path: 'src/components/RetroEffects', status: 'canonical' },
301
+ spritekit: { status: 'planned', note: 'Spacewar uses CRT shader natively; component is React-web-only' },
302
+ },
303
+ changelog: [
304
+ { version: '0.30.0', type: 'added', description: 'bootOnce — gate CGA turn-on to once per browser tab/session via sessionStorage' },
305
+ { version: '0.28.0', type: 'added', description: 'boot prop — CGA monitor turn-on sequence on mount (~650ms); onBootComplete callback' },
306
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first' },
307
+ { version: '0.8.0', type: 'changed', description: 'Align CRT animation values with design-craft spec' },
308
+ { version: '0.3.0', type: 'added', description: 'Initial RetroEffects — scanlines, glow, flicker, bloom, intensity, powered' },
309
+ ],
310
+ },
140
311
  Skeleton: { origin: 'eidotter', consumers: ['dmnctech'], originNote: 'DOS/CGA loading placeholder — shade-character rows (\u2591\u2592\u2593) with CRT hum-bar band rolling top\u2192bottom (DMNC-1018)' },
141
- Stat: { origin: 'steuerdash', consumers: ['steuerdash'], originNote: 'Key-value display created for tax dashboard' },
142
- Switch: { origin: 'eidotter', consumers: [] },
143
- FilterBar: { origin: 'eidotter', consumers: ['lifelines', 'rizomorf'], originNote: 'Multi-select toggle group for faceted filtering' },
312
+ Stat: {
313
+ origin: 'steuerdash',
314
+ consumers: ['steuerdash'],
315
+ since: '0.3.0',
316
+ originNote: 'Key-value metric display created for tax dashboard — optional trend arrow and TextScramble animation',
317
+ variants: {
318
+ 'size:sm': { description: 'Compact metric for dense dashboards', since: '0.16.0' },
319
+ 'size:md': { description: 'Standard metric display', since: '0.3.0', usedBy: ['steuerdash'] },
320
+ 'size:lg': { description: 'Hero metric for prominent KPIs', since: '0.16.0', usedBy: ['steuerdash'] },
321
+ 'trend:up': { description: 'Upward trend indicator (green arrow)', since: '0.3.0', usedBy: ['steuerdash'] },
322
+ 'trend:down': { description: 'Downward trend indicator (red arrow)', since: '0.3.0', usedBy: ['steuerdash'] },
323
+ 'trend:neutral': { description: 'No change indicator (muted dash)', since: '0.3.0' },
324
+ },
325
+ platforms: {
326
+ react: { path: 'src/components/Stat', status: 'canonical' },
327
+ swiftui: { status: 'planned' },
328
+ },
329
+ changelog: [
330
+ { version: '0.19.0', type: 'changed', description: 'Replace hardcoded font-size pixels with V.37 dos tokens' },
331
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2); deprecate size aliases; add size:sm/lg' },
332
+ { version: '0.3.0', type: 'added', description: 'Initial Stat — label, value, trend, trendValue, scramble prop' },
333
+ ],
334
+ },
335
+ Switch: {
336
+ origin: 'eidotter',
337
+ consumers: [],
338
+ since: '0.3.0',
339
+ variants: {
340
+ 'size:sm': { description: 'Compact 20px toggle for dense layouts', since: '0.16.0' },
341
+ 'size:md': { description: 'Standard 24px toggle', since: '0.3.0' },
342
+ 'type:default': { description: 'Full-width thumb track', since: '0.3.0' },
343
+ 'type:slim': { description: 'Narrow pill track for inline use', since: '0.3.0' },
344
+ },
345
+ platforms: {
346
+ react: { path: 'src/components/Switch', status: 'canonical' },
347
+ swiftui: { status: 'planned' },
348
+ },
349
+ changelog: [
350
+ { version: '0.16.0', type: 'changed', description: 'Migrate to React Aria + Tailwind-first (Wave 1); add size:sm variant' },
351
+ { version: '0.9.0', type: 'changed', description: 'Replace text-primary with cga-amber' },
352
+ { version: '0.8.0', type: 'changed', description: 'Design-craft animation polish — toggle glow transition' },
353
+ { version: '0.3.0', type: 'added', description: 'Initial Switch — controlled/uncontrolled, label, type:default/slim' },
354
+ ],
355
+ },
356
+ FilterBar: {
357
+ origin: 'eidotter',
358
+ consumers: ['lifelines', 'rizomorf'],
359
+ since: '0.7.0',
360
+ originNote: 'Multi-select toggle group for faceted filtering — keyboard nav, showAll, per-item color',
361
+ variants: {
362
+ 'mode:multi': { description: 'Multiple filters selectable simultaneously', since: '0.7.0', usedBy: ['lifelines', 'rizomorf'] },
363
+ 'mode:single': { description: 'Single active filter (radio behavior)', since: '0.7.0' },
364
+ 'size:sm': { description: 'Compact filter chips', since: '0.16.0' },
365
+ 'size:md': { description: 'Standard filter chips', since: '0.7.0', usedBy: ['lifelines', 'rizomorf'] },
366
+ 'size:lg': { description: 'Large filter chips for touch targets', since: '0.16.0' },
367
+ },
368
+ platforms: {
369
+ react: { path: 'src/components/FilterBar', status: 'canonical' },
370
+ },
371
+ changelog: [
372
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 3); deprecate size aliases' },
373
+ { version: '0.13.0', type: 'changed', description: 'Replace raw <button> with AriaButton for filter chips' },
374
+ { version: '0.7.0', type: 'added', description: 'Initial FilterBar — multi/single mode, items array, showAll, keyboard nav' },
375
+ ],
376
+ },
144
377
  Tag: {
145
378
  origin: 'eidotter',
146
379
  consumers: ['lifelines', 'rizomorf'],
@@ -163,12 +396,154 @@ export const componentRegistry = {
163
396
  { version: '0.7.0', type: 'added', description: 'Initial Tag and TagGroup components' },
164
397
  ],
165
398
  },
166
- Tabs: { origin: 'eidotter', consumers: ['steuerdash'] },
167
- Terminal: { origin: 'eidotter', consumers: ['rizomorf'] },
168
- TimelineNode: { origin: 'lifelines', consumers: ['lifelines', 'rizomorf'], originNote: 'Timeline markers from Lifelines project' },
169
- InlineExpand: { origin: 'rizomorf', consumers: ['rizomorf'], originNote: 'Inline disclosure widget for expanding text within prose' },
170
- Separator: { origin: 'rizomorf', consumers: ['rizomorf'], originNote: 'Horizontal/vertical divider for content separation' },
171
- TimelineContainer: { origin: 'lifelines', consumers: ['lifelines'], originNote: 'Multi-zoom timeline with year/month/day/hour views. Supports interactive, static, and feed modes.' },
399
+ Tabs: {
400
+ origin: 'eidotter',
401
+ consumers: ['steuerdash'],
402
+ since: '0.3.0',
403
+ variants: {
404
+ 'variant:underline': { description: 'Underline indicator tab bar (default)', since: '0.3.0', usedBy: ['steuerdash'] },
405
+ 'variant:pills': { description: 'Filled pill tabs', since: '0.3.0' },
406
+ 'size:sm': { description: 'Compact tabs for dense layouts', since: '0.16.0' },
407
+ 'size:md': { description: 'Standard tab height', since: '0.3.0', usedBy: ['steuerdash'] },
408
+ 'size:lg': { description: 'Large touch-friendly tabs', since: '0.16.0' },
409
+ },
410
+ platforms: {
411
+ react: { path: 'src/components/Tabs', status: 'canonical' },
412
+ swiftui: { status: 'planned' },
413
+ },
414
+ changelog: [
415
+ { version: '0.17.0', type: 'changed', description: 'Migrate to React Aria TabList/Tab/TabPanel (Wave 5)' },
416
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 3); deprecate size aliases; JS-measured indicator via ResizeObserver' },
417
+ { version: '0.11.0', type: 'changed', description: 'Dynamic text scaling — rem tokens for WCAG SC 1.4.4' },
418
+ { version: '0.9.0', type: 'changed', description: 'Replace text-primary with cga-amber' },
419
+ { version: '0.3.0', type: 'added', description: 'Initial Tabs — tabs array, activeTab, onSelectionChange, animated indicator' },
420
+ ],
421
+ },
422
+ Terminal: {
423
+ origin: 'eidotter',
424
+ consumers: ['rizomorf'],
425
+ since: '0.2.0',
426
+ originNote: 'DOS window frame with px-based bitmap font sizing — legacy BEM, not migrated to Tailwind-first',
427
+ variants: {
428
+ 'size:sm': { description: 'Compact 320px window', since: '0.2.0' },
429
+ 'size:md': { description: 'Standard 480px window', since: '0.2.0', usedBy: ['rizomorf'] },
430
+ 'size:lg': { description: 'Full-width window', since: '0.2.0' },
431
+ 'state:active': { description: 'Focused window with amber title bar', since: '0.2.0', usedBy: ['rizomorf'] },
432
+ 'state:inactive': { description: 'Unfocused window with dimmed title bar', since: '0.2.0' },
433
+ 'state:minimized': { description: 'Collapsed to title bar only', since: '0.2.0' },
434
+ },
435
+ platforms: {
436
+ react: { path: 'src/components/Terminal', status: 'canonical' },
437
+ },
438
+ changelog: [
439
+ { version: '0.30.0', type: 'changed', description: 'A11y keyboard focus fixes for window controls' },
440
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
441
+ { version: '0.14.0', type: 'changed', description: 'Breaking: minimizable/maximizable/closeable default to false — consumers must opt in' },
442
+ { version: '0.13.0', type: 'changed', description: 'Replace raw <button> with AriaButton for window controls' },
443
+ { version: '0.2.0', type: 'added', description: 'Initial Terminal — title bar, window controls, resizable, autoFocus, size/state variants' },
444
+ ],
445
+ },
446
+ TimelineNode: {
447
+ origin: 'lifelines',
448
+ consumers: ['lifelines', 'rizomorf'],
449
+ since: '0.4.0',
450
+ originNote: 'Timeline markers extracted from Lifelines — sits on the axis line, content-box sizing',
451
+ variants: {
452
+ 'shape:circle': { description: 'Circular node marker (default)', since: '0.4.0', usedBy: ['lifelines', 'rizomorf'] },
453
+ 'shape:square': { description: 'Square node marker', since: '0.4.0' },
454
+ 'shape:diamond': { description: 'Diamond (rotated square) node marker', since: '0.4.0' },
455
+ 'variant:default': { description: 'Muted amber node', since: '0.4.0', usedBy: ['lifelines', 'rizomorf'] },
456
+ 'variant:primary': { description: 'Full-brightness amber node with phosphor glow', since: '0.4.0', usedBy: ['lifelines'] },
457
+ 'variant:secondary': { description: 'Dimmed node for secondary events', since: '0.4.0' },
458
+ 'variant:accent': { description: 'CGA accent color node', since: '0.4.0' },
459
+ 'labelPosition:right': { description: 'Label to the right of the node (default)', since: '0.4.0', usedBy: ['lifelines', 'rizomorf'] },
460
+ 'labelPosition:left': { description: 'Label to the left of the node', since: '0.4.0' },
461
+ 'labelPosition:top': { description: 'Label above the node', since: '0.4.0' },
462
+ 'labelPosition:bottom': { description: 'Label below the node', since: '0.4.0' },
463
+ 'size:sm': { description: 'Compact 8px node', since: '0.16.0' },
464
+ 'size:md': { description: 'Standard 12px node', since: '0.4.0', usedBy: ['lifelines', 'rizomorf'] },
465
+ 'size:lg': { description: 'Large 16px node for emphasis', since: '0.16.0' },
466
+ },
467
+ platforms: {
468
+ react: { path: 'src/components/TimelineNode', status: 'canonical' },
469
+ },
470
+ changelog: [
471
+ { version: '0.35.0', type: 'changed', description: 'Pin marker box-sizing to content-box so dots stay on the axis line (DMNC-1090)' },
472
+ { version: '0.30.0', type: 'changed', description: 'A11y: high-contrast glow gap remediation' },
473
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; switch to Flexi IBM VGA font' },
474
+ { version: '0.4.0', type: 'added', description: 'Initial TimelineNode — shape, variant, labelPosition, size, isActive, onClick' },
475
+ ],
476
+ },
477
+ InlineExpand: {
478
+ origin: 'rizomorf',
479
+ consumers: ['rizomorf'],
480
+ since: '0.7.0',
481
+ originNote: 'Inline disclosure widget for expanding context within prose — sources with favicon fallback',
482
+ platforms: {
483
+ react: { path: 'src/components/InlineExpand', status: 'canonical' },
484
+ },
485
+ changelog: [
486
+ { version: '0.30.0', type: 'changed', description: 'Fix behavioral bugs — double-toggle edge case (DMNC-1063)' },
487
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first' },
488
+ { version: '0.8.0', type: 'added', description: 'sources prop — citation links with Google Favicons API fallback' },
489
+ { version: '0.7.0', type: 'added', description: 'Initial InlineExpand — expanded, defaultExpanded, onToggle, content slot' },
490
+ ],
491
+ },
492
+ Separator: {
493
+ origin: 'rizomorf',
494
+ consumers: ['rizomorf'],
495
+ since: '0.8.0',
496
+ originNote: 'Horizontal/vertical divider for content separation — ported from rizomorf',
497
+ variants: {
498
+ 'orientation:horizontal': { description: 'Full-width horizontal rule (default)', since: '0.8.0', usedBy: ['rizomorf'] },
499
+ 'orientation:vertical': { description: 'Inline vertical divider for flex/grid rows', since: '0.8.0' },
500
+ },
501
+ platforms: {
502
+ react: { path: 'src/components/Separator', status: 'canonical' },
503
+ },
504
+ changelog: [
505
+ { version: '0.16.0', type: 'changed', description: 'Migrate to Tailwind-first (Wave 2)' },
506
+ { version: '0.9.0', type: 'changed', description: 'Replace text-primary with cga-amber' },
507
+ { version: '0.8.0', type: 'added', description: 'Initial Separator — horizontal/vertical orientation, ported from rizomorf' },
508
+ ],
509
+ },
510
+ TimelineContainer: {
511
+ origin: 'lifelines',
512
+ consumers: ['lifelines'],
513
+ since: '0.8.0',
514
+ originNote: 'Multi-zoom timeline with year/month/day/hour views. Supports interactive, static, feed, and master-detail modes.',
515
+ variants: {
516
+ 'mode:interactive': { description: 'Full drill-down with keyboard shortcuts and Ctrl+scroll zoom', since: '0.8.0', usedBy: ['lifelines'] },
517
+ 'mode:static': { description: 'Read-only snapshot — no zoom or selection controls', since: '0.8.0' },
518
+ 'mode:feed': { description: 'Paginated vertical list with LOAD MORE — append-safe', since: '0.21.0', usedBy: ['lifelines'] },
519
+ 'mode:master-detail': { description: 'Split-pane with list rail + detail panel', since: '0.34.0' },
520
+ 'sortOrder:asc': { description: 'Oldest entries first', since: '0.8.0' },
521
+ 'sortOrder:desc': { description: 'Newest entries first (default)', since: '0.8.0', usedBy: ['lifelines'] },
522
+ },
523
+ platforms: {
524
+ react: { path: 'src/components/TimelineContainer', status: 'canonical' },
525
+ },
526
+ changelog: [
527
+ { version: '0.34.0', type: 'added', description: 'master-detail layout mode — split-pane list rail + detail panel (DMNC-878)' },
528
+ { version: '0.21.0', type: 'added', description: 'feed mode — pageSize, onLoadMore, DOS-styled LOAD MORE button' },
529
+ { version: '0.21.0', type: 'added', description: 'renderEntry prop — pluggable entry renderer with defaultRender() opt-in' },
530
+ { version: '0.9.0', type: 'changed', description: 'Responsive layout with CSS container queries' },
531
+ { version: '0.8.0', type: 'added', description: 'Initial TimelineContainer — year/month/day/hour drill-down, migrated from Lifelines' },
532
+ ],
533
+ },
534
+ TextScramble: {
535
+ origin: 'eidotter',
536
+ consumers: [],
537
+ since: '0.10.0',
538
+ originNote: 'DOS text decode effect — rAF-driven character scramble reveal; used by Stat (scramble prop) and standalone',
539
+ platforms: {
540
+ react: { path: 'src/components/TextScramble', status: 'canonical' },
541
+ },
542
+ changelog: [
543
+ { version: '0.18.0', type: 'changed', description: 'Migrate BEM to Tailwind-first; adopt cn() and forwardRef' },
544
+ { version: '0.10.0', type: 'added', description: 'Initial TextScramble — children string, speed, characters, delay; rAF-based reveal' },
545
+ ],
546
+ },
172
547
  // Chat components
173
548
  ChatMessage: { origin: 'eidotter', consumers: [], originNote: 'Single chat message with role-based DOS styling and streaming cursor' },
174
549
  ChatHistory: { origin: 'eidotter', consumers: [], originNote: 'Scrollable message list with auto-scroll and role="log" accessibility' },
@@ -208,6 +583,37 @@ export const componentRegistry = {
208
583
  { version: '0.20.0', type: 'added', description: 'Initial CmdPalette — generic items with keywords, mod+k global hotkey, custom renderItem, useId() per-instance DOM ids, selected-clamp on items mutation' },
209
584
  ],
210
585
  },
586
+ // Ported from the Tracker habit app (DMNC-1040)
587
+ ProgressRing: {
588
+ origin: 'tracker',
589
+ consumers: ['tracker'],
590
+ since: '0.37.0',
591
+ originNote: '16-segment quantized progress ring (SVG). Powers Tracker’s hold-to-complete feedback ring.',
592
+ platforms: { react: { path: 'src/components/ProgressRing', status: 'canonical' } },
593
+ changelog: [
594
+ { version: '0.37.0', type: 'added', description: 'Initial ProgressRing — discrete quantized segment arcs, phosphor glow, centre label, currentColor theming' },
595
+ ],
596
+ },
597
+ CalendarHeatmap: {
598
+ origin: 'tracker',
599
+ consumers: ['tracker'],
600
+ since: '0.37.0',
601
+ originNote: 'Block-character month calendar heatmap (█ ░ · ▒). Domain-agnostic — takes a date→status map, not a domain object.',
602
+ platforms: { react: { path: 'src/components/CalendarHeatmap', status: 'canonical' } },
603
+ changelog: [
604
+ { version: '0.37.0', type: 'added', description: 'Initial CalendarHeatmap — UTC date maths, Monday/Sunday week start, legend, successColor override, glyph overrides' },
605
+ ],
606
+ },
607
+ HoldToComplete: {
608
+ origin: 'tracker',
609
+ consumers: ['tracker'],
610
+ since: '0.37.0',
611
+ originNote: 'Press-and-hold-to-complete interaction with ProgressRing feedback. Pointer + keyboard, reduced-motion aware.',
612
+ platforms: { react: { path: 'src/components/HoldToComplete', status: 'canonical' } },
613
+ changelog: [
614
+ { version: '0.37.0', type: 'added', description: 'Initial HoldToComplete — rAF hold animation with accelerating ease, onHoldProgress hook, Enter/Space to complete' },
615
+ ],
616
+ },
211
617
  };
212
618
  // ---------------------------------------------------------------------------
213
619
  // Utility functions