@stackwright-pro/pulse 0.2.0 → 0.2.1-alpha.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/README.md CHANGED
@@ -20,6 +20,7 @@ pnpm add @stackwright-pro/pulse
20
20
  ```
21
21
 
22
22
  **Peer Dependencies:**
23
+
23
24
  ```bash
24
25
  pnpm add react react-dom @tanstack/react-query
25
26
  ```
@@ -33,10 +34,7 @@ import { Pulse, PulseIndicator } from '@stackwright-pro/pulse';
33
34
 
34
35
  function EquipmentPanel() {
35
36
  return (
36
- <Pulse
37
- fetcher={() => fetch('/api/equipment').then(r => r.json())}
38
- interval={5000}
39
- >
37
+ <Pulse fetcher={() => fetch('/api/equipment').then((r) => r.json())} interval={5000}>
40
38
  {(equipment, meta) => (
41
39
  <>
42
40
  <PulseIndicator meta={meta} />
@@ -64,7 +62,7 @@ const EquipmentSchema = z.object({
64
62
  function EquipmentPanel() {
65
63
  return (
66
64
  <Pulse
67
- fetcher={() => fetch('/api/equipment').then(r => r.json())}
65
+ fetcher={() => fetch('/api/equipment').then((r) => r.json())}
68
66
  schema={EquipmentSchema}
69
67
  interval={5000}
70
68
  >
@@ -88,8 +86,8 @@ function CustomEquipmentPanel() {
88
86
  maxStaleAge={60000}
89
87
  loadingState={<SkeletonLoader />}
90
88
  errorState={(meta) => (
91
- <ErrorBanner
92
- message="Failed to load equipment"
89
+ <ErrorBanner
90
+ message="Failed to load equipment"
93
91
  lastUpdated={meta.lastUpdated}
94
92
  onRetry={meta.refetch}
95
93
  />
@@ -147,27 +145,27 @@ function CustomEquipmentPanel() {
147
145
  ```tsx
148
146
  interface PulseProps<T> {
149
147
  // Core
150
- fetcher: () => Promise<T>; // Your data fetching function
148
+ fetcher: () => Promise<T>; // Your data fetching function
151
149
  children: (data: T, meta: PulseMeta) => React.ReactNode;
152
-
150
+
153
151
  // Polling config
154
- interval?: number; // Poll interval in ms (default: 5000, min: 1000)
155
- enabled?: boolean; // Enable/disable polling (default: true)
156
- retryCount?: number; // Number of retries on error (default: 3)
157
- refetchOnWindowFocus?: boolean; // Refetch on window focus (default: true)
158
-
152
+ interval?: number; // Poll interval in ms (default: 5000, min: 1000)
153
+ enabled?: boolean; // Enable/disable polling (default: true)
154
+ retryCount?: number; // Number of retries on error (default: 3)
155
+ refetchOnWindowFocus?: boolean; // Refetch on window focus (default: true)
156
+
159
157
  // State thresholds (in milliseconds)
160
- staleThreshold?: number; // Show stale warning (default: 30000)
161
- maxStaleAge?: number; // Show error state (default: 60000)
162
-
158
+ staleThreshold?: number; // Show stale warning (default: 30000)
159
+ maxStaleAge?: number; // Show error state (default: 60000)
160
+
163
161
  // Validation
164
- schema?: ZodSchema<T>; // Optional Zod schema
165
-
162
+ schema?: ZodSchema<T>; // Optional Zod schema
163
+
166
164
  // Custom state UI
167
165
  loadingState?: React.ReactNode;
168
166
  errorState?: React.ReactNode | ((meta: PulseMeta) => React.ReactNode);
169
167
  emptyState?: React.ReactNode;
170
- showStaleDataOnError?: boolean; // Show data during error (default: true)
168
+ showStaleDataOnError?: boolean; // Show data during error (default: true)
171
169
  }
172
170
  ```
173
171
 
@@ -175,12 +173,12 @@ interface PulseProps<T> {
175
173
 
176
174
  ```tsx
177
175
  interface PulseMeta {
178
- lastUpdated: Date; // Timestamp of last successful fetch
179
- isStale: boolean; // Data older than staleThreshold
180
- isError: boolean; // Data older than maxStaleAge or fetch failed
181
- isLoading: boolean; // Currently fetching
182
- errorCount: number; // Number of consecutive errors
183
- refetch: () => void; // Manual refresh trigger
176
+ lastUpdated: Date; // Timestamp of last successful fetch
177
+ isStale: boolean; // Data older than staleThreshold
178
+ isError: boolean; // Data older than maxStaleAge or fetch failed
179
+ isLoading: boolean; // Currently fetching
180
+ errorCount: number; // Number of consecutive errors
181
+ refetch: () => void; // Manual refresh trigger
184
182
  state: 'loading' | 'live' | 'stale' | 'error';
185
183
  }
186
184
  ```
@@ -189,14 +187,14 @@ interface PulseMeta {
189
187
 
190
188
  ```tsx
191
189
  interface PulseIndicatorProps {
192
- meta: PulseMeta; // Required meta object
193
- showSeconds?: boolean; // Show "Xs ago" (default: true)
194
- className?: string; // Additional CSS classes
190
+ meta: PulseMeta; // Required meta object
191
+ showSeconds?: boolean; // Show "Xs ago" (default: true)
192
+ className?: string; // Additional CSS classes
195
193
  labels?: {
196
- live?: string; // Custom live label
197
- syncing?: string; // Custom syncing label
198
- stale?: string; // Custom stale label
199
- error?: string; // Custom error label
194
+ live?: string; // Custom live label
195
+ syncing?: string; // Custom syncing label
196
+ stale?: string; // Custom stale label
197
+ error?: string; // Custom error label
200
198
  };
201
199
  }
202
200
  ```
@@ -228,12 +226,12 @@ const result = usePulse<T>({
228
226
 
229
227
  The `fetcher` prop accepts **any** async function. This makes Pulse work with:
230
228
 
231
- | Source | Example Fetcher |
232
- |--------|-----------------|
229
+ | Source | Example Fetcher |
230
+ | ------------ | ---------------------------------------------- |
233
231
  | REST/OpenAPI | `() => fetch('/api/data').then(r => r.json())` |
234
- | WebSocket | `() => websocket.nextMessage()` |
235
- | GraphQL | `() => client.query({ query: MY_QUERY })` |
236
- | SSE | `() => eventSource.nextEvent()` |
232
+ | WebSocket | `() => websocket.nextMessage()` |
233
+ | GraphQL | `() => client.query({ query: MY_QUERY })` |
234
+ | SSE | `() => eventSource.nextEvent()` |
237
235
 
238
236
  See [SOURCE_ADAPTERS.md](./docs/SOURCE_ADAPTERS.md) for planned first-party adapter documentation.
239
237
 
@@ -343,6 +341,333 @@ For a full working demo, see [examples/marine-logistics-demo/src/pages/dashboard
343
341
 
344
342
  See [docs/SOURCE_ADAPTERS.md](./docs/SOURCE_ADAPTERS.md) for planned adapter documentation.
345
343
 
344
+ ## Collection Binding (Phase 3 - Killer Pro Feature)
345
+
346
+ The collection binding system connects **OpenAPI data** → **Prebuild** → **Pulse** → **Dashboard Components** for a complete live data experience.
347
+
348
+ ### Architecture
349
+
350
+ ```
351
+ ┌──────────────────────────────────────────────────────────────────────────┐
352
+ │ DASHBOARD OTTER │
353
+ │ Generates YAML with {{ collection.field }} template syntax │
354
+ └─────────────────────────────────┬────────────────────────────────────────┘
355
+
356
+
357
+ ┌──────────────────────────────────────────────────────────────────────────┐
358
+ │ stackwright.yml │
359
+ │ integrations: │
360
+ │ - name: logistics-api │
361
+ │ spec: ./openapi.yaml │
362
+ │ collections: │
363
+ │ - endpoint: /equipment │
364
+ └─────────────────────────────────┬────────────────────────────────────────┘
365
+
366
+
367
+ ┌──────────────────────────────────────────────────────────────────────────┐
368
+ │ PREBUILD (pnpm prebuild) │
369
+ │ OpenAPIPlugin generates: │
370
+ │ - src/generated/logistics-api/provider.ts (CollectionProvider) │
371
+ │ - src/generated/logistics-api/schemas.ts (Zod schemas) │
372
+ │ - src/generated/logistics-api/types.ts (TypeScript types) │
373
+ └─────────────────────────────────┬────────────────────────────────────────┘
374
+
375
+
376
+ ┌──────────────────────────────────────────────────────────────────────────┐
377
+ │ PulseCollectionProvider │
378
+ │ Wraps dashboard with live collection context │
379
+ │ - Creates fetchers from prebuild providers │
380
+ │ - Polls at configured intervals (5s, 30s, 300s, etc.) │
381
+ │ - Provides data via React context │
382
+ └─────────────────────────────────┬────────────────────────────────────────┘
383
+
384
+
385
+ ┌──────────────────────────────────────────────────────────────────────────┐
386
+ │ Pulse-Enabled Components │
387
+ │ MetricCardPulse, DataTablePulse, StatusBadgePulse │
388
+ │ - Auto-bind to collection data │
389
+ │ - Support template syntax {{ collection.field }} │
390
+ │ - Built-in aggregation (count, sum, avg) │
391
+ │ - Live indicator with stale/error states │
392
+ └──────────────────────────────────────────────────────────────────────────┘
393
+ ```
394
+
395
+ ### Quick Start
396
+
397
+ #### 1. Configure OpenAPI in `stackwright.yml`
398
+
399
+ ```yaml
400
+ integrations:
401
+ - name: logistics-api
402
+ spec: ./openapi.yaml
403
+ auth:
404
+ type: bearer
405
+ token: ${API_TOKEN}
406
+ collections:
407
+ - endpoint: /equipment
408
+ slug_field: id
409
+ - endpoint: /locations
410
+ slug_field: id
411
+ ```
412
+
413
+ #### 2. Run Prebuild
414
+
415
+ ```bash
416
+ pnpm prebuild
417
+ # Generates src/generated/logistics-api/*
418
+ ```
419
+
420
+ #### 3. Use Pulse Components in Pages
421
+
422
+ ```yaml
423
+ # pages/dashboard/content.yml
424
+ content:
425
+ meta:
426
+ title: 'Live Equipment Dashboard'
427
+
428
+ content_items:
429
+ # Wrap with Pulse provider for live data
430
+ - type: pulse_provider
431
+ label: equipment-live
432
+ collections:
433
+ - name: equipment
434
+ endpoint: /api/equipment
435
+ refreshInterval: 5000
436
+ items:
437
+ - type: grid
438
+ columns: 4
439
+ items:
440
+ - type: metric_card_pulse
441
+ collection: equipment
442
+ field: items.length
443
+ label: 'Total Equipment'
444
+ icon: Truck
445
+
446
+ - type: metric_card_pulse
447
+ collection: equipment
448
+ field: items
449
+ label: 'Active'
450
+ aggregate: count
451
+ aggregateField: status
452
+ filter: '{"status": "active"}'
453
+ icon: CheckCircle
454
+
455
+ - type: data_table_pulse
456
+ collection: equipment
457
+ columns:
458
+ - field: id
459
+ header: ID
460
+ sortable: true
461
+ - field: name
462
+ header: Name
463
+ - field: status
464
+ header: Status
465
+ type: badge
466
+ sortBy: name
467
+ sortDirection: asc
468
+ limit: 20
469
+ ```
470
+
471
+ ### Component Reference
472
+
473
+ ### `pulse_provider`
474
+
475
+ Wraps content with live collection data. All child Pulse components automatically bind to these collections.
476
+
477
+ ```yaml
478
+ - type: pulse_provider
479
+ label: equipment-live
480
+ collections:
481
+ - name: equipment # Collection name for binding
482
+ endpoint: /api/equipment # OpenAPI endpoint
483
+ refreshInterval: 5000 # Poll every 5 seconds
484
+ filter: # Optional server-side filter
485
+ status: active
486
+ items:
487
+ # Child components here
488
+ ```
489
+
490
+ Props:
491
+ | Prop | Type | Description |
492
+ |------|------|-------------|
493
+ | `label` | string | Unique identifier |
494
+ | `collections` | CollectionBinding[] | Collections to fetch |
495
+ | `collections[].name` | string | Collection name for binding |
496
+ | `collections[].endpoint` | string | API endpoint path |
497
+ | `collections[].refreshInterval` | number | Poll interval in ms (default: 5000) |
498
+ | `collections[].filter` | object | Server-side filter params |
499
+
500
+ ### `metric_card_pulse`
501
+
502
+ KPI card with auto-bound live data.
503
+
504
+ ```yaml
505
+ - type: metric_card_pulse
506
+ collection: equipment # Collection to bind
507
+ field: items.length # Field or expression
508
+ label: 'Total Equipment' # Display label
509
+ icon: Truck # Icon name
510
+ aggregate: count # Optional: count, sum, avg
511
+ aggregateField: status # Field for aggregation
512
+ filter: '{"status": "active"}' # Optional: filter items
513
+ ```
514
+
515
+ Props:
516
+ | Prop | Type | Description |
517
+ |------|------|-------------|
518
+ | `collection` | string | Collection name |
519
+ | `field` | string | Field path (supports dots) |
520
+ | `label` | string | Card label |
521
+ | `icon` | ReactNode | Icon component |
522
+ | `color` | string | Accent color (hex) |
523
+ | `trend` | 'up' \| 'down' \| 'stable' | Trend direction |
524
+ | `trendValue` | string | Trend display value |
525
+ | `aggregate` | 'count' \| 'sum' \| 'avg' | Aggregate function |
526
+ | `aggregateField` | string | Field for aggregation |
527
+ | `filter` | string | JSON filter string |
528
+
529
+ ### `data_table_pulse`
530
+
531
+ Sortable table with live data binding.
532
+
533
+ ```yaml
534
+ - type: data_table_pulse
535
+ collection: equipment
536
+ columns:
537
+ - field: id
538
+ header: ID
539
+ sortable: true
540
+ - field: name
541
+ header: Name
542
+ - field: status
543
+ header: Status
544
+ type: badge
545
+ sortBy: name
546
+ sortDirection: asc
547
+ limit: 20
548
+ ```
549
+
550
+ Props:
551
+ | Prop | Type | Description |
552
+ |------|------|-------------|
553
+ | `collection` | string | Collection name |
554
+ | `columns` | Column[] | Column definitions |
555
+ | `sortBy` | string | Field to sort by |
556
+ | `sortDirection` | 'asc' \| 'desc' | Sort direction |
557
+ | `filter` | string | JSON filter string |
558
+ | `limit` | number | Max items to show |
559
+ | `onRowClick` | function | Row click handler |
560
+ | `emptyMessage` | string | Empty state message |
561
+
562
+ ### `status_badge_pulse`
563
+
564
+ Status indicator with live data binding.
565
+
566
+ ```yaml
567
+ - type: status_badge_pulse
568
+ collection: equipment
569
+ field: items[0].status
570
+ label: 'System Status'
571
+ pulse: true
572
+ ```
573
+
574
+ Props:
575
+ | Prop | Type | Description |
576
+ |------|------|-------------|
577
+ | `collection` | string | Collection name |
578
+ | `field` | string | Status field path |
579
+ | `label` | string | Display label |
580
+ | `pulse` | boolean | Show pulse animation |
581
+ | `statusMap` | object | Custom value → status mapping |
582
+
583
+ ### Hooks
584
+
585
+ For custom component integration:
586
+
587
+ ```tsx
588
+ import {
589
+ usePulseCollections, // Full context
590
+ useCollection, // Single collection
591
+ useCollectionField, // Specific field
592
+ useTemplateResolution, // Resolve templates
593
+ resolveTemplate, // Static template resolver
594
+ } from '@stackwright-pro/pulse';
595
+
596
+ // Get full context
597
+ const { collections, isLoading, getField } = usePulseCollections();
598
+
599
+ // Get single collection
600
+ const equipment = useCollection('equipment');
601
+ // Returns: { items, count, meta, ... }
602
+
603
+ // Get specific field
604
+ const count = useCollectionField('equipment', 'items.length');
605
+
606
+ // Resolve template anywhere
607
+ const templateResolution = useTemplateResolution();
608
+ const value = templateResolution('{{ equipment.count }}');
609
+ ```
610
+
611
+ ### Template Syntax
612
+
613
+ The `{{ collection.field }}` syntax is automatically resolved:
614
+
615
+ | Pattern | Example | Result |
616
+ | ------------ | ------------------------------- | --------------------- |
617
+ | Count | `{{ equipment.count }}` | Number of items |
618
+ | Array length | `{{ equipment.items.length }}` | Array size |
619
+ | Nested field | `{{ equipment.items[0].name }}` | First item's name |
620
+ | Aggregate | `{{ equipment.status.active }}` | Count of active items |
621
+
622
+ ### ISR Intervals
623
+
624
+ Configure refresh intervals based on data freshness needs:
625
+
626
+ | Interval | Use Case | Example |
627
+ | -------- | ------------- | ------------------ |
628
+ | 5000ms | Real-time | Live dashboard |
629
+ | 30000ms | Near-realtime | Status updates |
630
+ | 300000ms | Hourly | Reports, summaries |
631
+
632
+ ### Error Handling
633
+
634
+ Pulse components handle errors gracefully:
635
+
636
+ ```tsx
637
+ // With error state
638
+ <DataTablePulse
639
+ collection="equipment"
640
+ columns={columns}
641
+ fallback={<ErrorBanner />}
642
+ />
643
+
644
+ // With stale data (continue showing data during errors)
645
+ <MetricCardPulse
646
+ collection="equipment"
647
+ field="count"
648
+ showStaleData={true}
649
+ />
650
+ ```
651
+
652
+ ### Complete Example
653
+
654
+ See the marine-logistics demo for a full working example:
655
+
656
+ ```bash
657
+ pnpm prebuild # Generate providers
658
+ pnpm dev # Start dev server
659
+ ```
660
+
661
+ The dashboard will show live-updating KPI cards and data tables with auto-refresh!
662
+
663
+ ### Future: Real-time Providers
664
+
665
+ Coming soon:
666
+
667
+ - `createWebSocketFetcher()` - WebSocket adapter
668
+ - `createSSEFetcher()` - Server-Sent Events adapter
669
+ - `createGraphQLFetcher()` - GraphQL polling adapter
670
+
346
671
  ## License
347
672
 
348
- PROPRIETARY - All rights reserved.
673
+ PROPRIETARY - All rights reserved.
@@ -0,0 +1,10 @@
1
+ // src/collection/collectionData.ts
2
+ async function getCollectionData(collection) {
3
+ console.warn(
4
+ `[collectionData] Stub called for collection: ${collection}. Run prebuild to generate real data fetchers.`
5
+ );
6
+ return [];
7
+ }
8
+ export {
9
+ getCollectionData
10
+ };
package/dist/index.d.mts CHANGED
@@ -230,4 +230,82 @@ declare class PulseValidationError extends Error {
230
230
  }[];
231
231
  }
232
232
 
233
- export { Pulse, PulseEmptyState, PulseErrorState, PulseIndicator, type PulseIndicatorProps, PulseLoadingState, type PulseMeta, type PulseOptions, type PulseProps, PulseStaleState, type PulseState, PulseSyncingState, PulseValidationError, createPulseValidator, usePulse, useStreaming };
233
+ interface CollectionBinding {
234
+ collection: string;
235
+ endpoint: string;
236
+ slugField?: string;
237
+ refreshInterval?: number;
238
+ schema?: unknown;
239
+ }
240
+ interface CollectionData {
241
+ items: unknown[];
242
+ count: number;
243
+ meta: PulseMeta;
244
+ [key: string]: unknown;
245
+ }
246
+ interface PulseCollectionContextValue {
247
+ collections: Record<string, CollectionData>;
248
+ isLoading: boolean;
249
+ getCollection: (name: string) => CollectionData | null;
250
+ getField: (collection: string, field: string) => unknown;
251
+ }
252
+ declare function resolveTemplate(template: string, collections: Record<string, CollectionData>): unknown;
253
+ declare function PulseCollectionProvider({ collections: collectionConfigs, children, fallback, }: {
254
+ collections: CollectionBinding[];
255
+ children: React.ReactNode;
256
+ fallback?: React.ReactNode;
257
+ }): react_jsx_runtime.JSX.Element;
258
+ declare function usePulseCollections(): PulseCollectionContextValue;
259
+ declare function useCollection(collectionName: string): CollectionData | null;
260
+ declare function useCollectionField(collectionName: string, field: string): unknown;
261
+ declare function useTemplateResolution(): (template: string) => unknown;
262
+
263
+ interface MetricCardPulseProps {
264
+ /** Collection name to bind to */
265
+ collection: string;
266
+ /** Field to display (supports {{ collection.field }} syntax) */
267
+ field: string;
268
+ /** Static label override */
269
+ label?: string;
270
+ /** Icon to show */
271
+ icon?: React.ReactNode;
272
+ /** Color for the card accent */
273
+ color?: string;
274
+ /** Trend direction: 'up' | 'down' | 'stable' */
275
+ trend?: 'up' | 'down' | 'stable';
276
+ /** Static trend value override */
277
+ trendValue?: string;
278
+ /** Aggregate function for array data: 'count' | 'sum' | 'avg' */
279
+ aggregate?: 'count' | 'sum' | 'avg';
280
+ /** Path within items to use for aggregation */
281
+ aggregateField?: string;
282
+ }
283
+ /**
284
+ * MetricCard with live data binding to collections
285
+ *
286
+ * Examples:
287
+ * ```yaml
288
+ * - type: metric_card_pulse
289
+ * collection: equipment
290
+ * field: items.length # count of equipment
291
+ * label: "Total Equipment"
292
+ * icon: Truck
293
+ * ```
294
+ */
295
+ declare function MetricCardPulse({ collection, field, label, icon, color, trend, trendValue, aggregate, aggregateField, }: MetricCardPulseProps): react_jsx_runtime.JSX.Element;
296
+
297
+ /**
298
+ * Register Pulse collection binding components
299
+ *
300
+ * Call this AFTER registerDisplayComponents() in _app.tsx:
301
+ * ```ts
302
+ * import { registerDisplayComponents } from '@stackwright-pro/display-components';
303
+ * import { registerPulseComponents } from '@stackwright-pro/pulse';
304
+ *
305
+ * registerDisplayComponents();
306
+ * registerPulseComponents();
307
+ * ```
308
+ */
309
+ declare function registerPulseComponents(): void;
310
+
311
+ export { type CollectionBinding, MetricCardPulse, Pulse, type PulseCollectionContextValue, PulseCollectionProvider, PulseEmptyState, PulseErrorState, PulseIndicator, type PulseIndicatorProps, PulseLoadingState, type PulseMeta, type PulseOptions, type PulseProps, PulseStaleState, type PulseState, PulseSyncingState, PulseValidationError, createPulseValidator, registerPulseComponents, resolveTemplate, useCollection, useCollectionField, usePulse, usePulseCollections, useStreaming, useTemplateResolution };
package/dist/index.d.ts CHANGED
@@ -230,4 +230,82 @@ declare class PulseValidationError extends Error {
230
230
  }[];
231
231
  }
232
232
 
233
- export { Pulse, PulseEmptyState, PulseErrorState, PulseIndicator, type PulseIndicatorProps, PulseLoadingState, type PulseMeta, type PulseOptions, type PulseProps, PulseStaleState, type PulseState, PulseSyncingState, PulseValidationError, createPulseValidator, usePulse, useStreaming };
233
+ interface CollectionBinding {
234
+ collection: string;
235
+ endpoint: string;
236
+ slugField?: string;
237
+ refreshInterval?: number;
238
+ schema?: unknown;
239
+ }
240
+ interface CollectionData {
241
+ items: unknown[];
242
+ count: number;
243
+ meta: PulseMeta;
244
+ [key: string]: unknown;
245
+ }
246
+ interface PulseCollectionContextValue {
247
+ collections: Record<string, CollectionData>;
248
+ isLoading: boolean;
249
+ getCollection: (name: string) => CollectionData | null;
250
+ getField: (collection: string, field: string) => unknown;
251
+ }
252
+ declare function resolveTemplate(template: string, collections: Record<string, CollectionData>): unknown;
253
+ declare function PulseCollectionProvider({ collections: collectionConfigs, children, fallback, }: {
254
+ collections: CollectionBinding[];
255
+ children: React.ReactNode;
256
+ fallback?: React.ReactNode;
257
+ }): react_jsx_runtime.JSX.Element;
258
+ declare function usePulseCollections(): PulseCollectionContextValue;
259
+ declare function useCollection(collectionName: string): CollectionData | null;
260
+ declare function useCollectionField(collectionName: string, field: string): unknown;
261
+ declare function useTemplateResolution(): (template: string) => unknown;
262
+
263
+ interface MetricCardPulseProps {
264
+ /** Collection name to bind to */
265
+ collection: string;
266
+ /** Field to display (supports {{ collection.field }} syntax) */
267
+ field: string;
268
+ /** Static label override */
269
+ label?: string;
270
+ /** Icon to show */
271
+ icon?: React.ReactNode;
272
+ /** Color for the card accent */
273
+ color?: string;
274
+ /** Trend direction: 'up' | 'down' | 'stable' */
275
+ trend?: 'up' | 'down' | 'stable';
276
+ /** Static trend value override */
277
+ trendValue?: string;
278
+ /** Aggregate function for array data: 'count' | 'sum' | 'avg' */
279
+ aggregate?: 'count' | 'sum' | 'avg';
280
+ /** Path within items to use for aggregation */
281
+ aggregateField?: string;
282
+ }
283
+ /**
284
+ * MetricCard with live data binding to collections
285
+ *
286
+ * Examples:
287
+ * ```yaml
288
+ * - type: metric_card_pulse
289
+ * collection: equipment
290
+ * field: items.length # count of equipment
291
+ * label: "Total Equipment"
292
+ * icon: Truck
293
+ * ```
294
+ */
295
+ declare function MetricCardPulse({ collection, field, label, icon, color, trend, trendValue, aggregate, aggregateField, }: MetricCardPulseProps): react_jsx_runtime.JSX.Element;
296
+
297
+ /**
298
+ * Register Pulse collection binding components
299
+ *
300
+ * Call this AFTER registerDisplayComponents() in _app.tsx:
301
+ * ```ts
302
+ * import { registerDisplayComponents } from '@stackwright-pro/display-components';
303
+ * import { registerPulseComponents } from '@stackwright-pro/pulse';
304
+ *
305
+ * registerDisplayComponents();
306
+ * registerPulseComponents();
307
+ * ```
308
+ */
309
+ declare function registerPulseComponents(): void;
310
+
311
+ export { type CollectionBinding, MetricCardPulse, Pulse, type PulseCollectionContextValue, PulseCollectionProvider, PulseEmptyState, PulseErrorState, PulseIndicator, type PulseIndicatorProps, PulseLoadingState, type PulseMeta, type PulseOptions, type PulseProps, PulseStaleState, type PulseState, PulseSyncingState, PulseValidationError, createPulseValidator, registerPulseComponents, resolveTemplate, useCollection, useCollectionField, usePulse, usePulseCollections, useStreaming, useTemplateResolution };