@lukso/transaction-view-headless 0.2.2-dev.a8c9315

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 (76) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/CHANGELOG.md +88 -0
  3. package/COMPONENT_ARCHITECTURE.md +190 -0
  4. package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
  5. package/CONTEXT_VARIANT_GUIDE.md +425 -0
  6. package/LAZY_LOADING_GUIDE.md +534 -0
  7. package/LICENSE +201 -0
  8. package/PLAYBACK_EXAMPLES.md +658 -0
  9. package/PLAYBACK_GUIDE.md +402 -0
  10. package/README.md +43 -0
  11. package/REGISTRY_SUMMARY.md +638 -0
  12. package/SUFFERING_ECONOMICS.md +346 -0
  13. package/SUFFERING_ECONOMICS.zip +0 -0
  14. package/VIEW_REGISTRATION_GUIDE.md +795 -0
  15. package/dist/composables/index.cjs +1321 -0
  16. package/dist/composables/index.cjs.map +1 -0
  17. package/dist/composables/index.d.cts +6 -0
  18. package/dist/composables/index.d.ts +6 -0
  19. package/dist/composables/index.js +1276 -0
  20. package/dist/composables/index.js.map +1 -0
  21. package/dist/index-CMLU1hKL.d.ts +374 -0
  22. package/dist/index-CSDjhiKV.d.cts +374 -0
  23. package/dist/index.cjs +1534 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +93 -0
  26. package/dist/index.d.ts +93 -0
  27. package/dist/index.js +1463 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/registry/index.cjs +522 -0
  30. package/dist/registry/index.cjs.map +1 -0
  31. package/dist/registry/index.d.cts +46 -0
  32. package/dist/registry/index.d.ts +46 -0
  33. package/dist/registry/index.js +487 -0
  34. package/dist/registry/index.js.map +1 -0
  35. package/dist/types/index.cjs +19 -0
  36. package/dist/types/index.cjs.map +1 -0
  37. package/dist/types/index.d.cts +203 -0
  38. package/dist/types/index.d.ts +203 -0
  39. package/dist/types/index.js +1 -0
  40. package/dist/types/index.js.map +1 -0
  41. package/dist/utils/index.cjs +217 -0
  42. package/dist/utils/index.cjs.map +1 -0
  43. package/dist/utils/index.d.cts +47 -0
  44. package/dist/utils/index.d.ts +47 -0
  45. package/dist/utils/index.js +188 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/view-loader-dD86QAlQ.d.cts +121 -0
  48. package/dist/view-loader-ii8AxiQR.d.ts +121 -0
  49. package/package.json +64 -0
  50. package/publish.log +0 -0
  51. package/src/composables/index.ts +59 -0
  52. package/src/composables/use-image-loader.ts +315 -0
  53. package/src/composables/use-transaction-playback.ts +396 -0
  54. package/src/composables/use-transaction-view.ts +309 -0
  55. package/src/config/lukso-config.ts +171 -0
  56. package/src/examples/context-aware-views.ts +300 -0
  57. package/src/examples/example-views.ts +197 -0
  58. package/src/examples/lit-plus-rn-strategy.ts +448 -0
  59. package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
  60. package/src/examples/opt-in-context-examples.ts +372 -0
  61. package/src/examples/optimized-view-definitions.ts +408 -0
  62. package/src/examples/vue-to-lit-compilation.ts +358 -0
  63. package/src/hooks/useAddressQuery.ts +135 -0
  64. package/src/hooks/useAddressResolution.ts +437 -0
  65. package/src/index.ts +36 -0
  66. package/src/registry/index.ts +13 -0
  67. package/src/registry/view-loader.ts +305 -0
  68. package/src/registry/view-registry.ts +135 -0
  69. package/src/types/index.ts +16 -0
  70. package/src/types/view-contexts.ts +72 -0
  71. package/src/types/view-matching.ts +173 -0
  72. package/src/utils/context-matcher.ts +165 -0
  73. package/src/utils/index.ts +5 -0
  74. package/src/utils/view-matcher.ts +338 -0
  75. package/tsconfig.json +20 -0
  76. package/tsup.config.ts +34 -0
@@ -0,0 +1,305 @@
1
+ import type {
2
+ FrameworkViewConfig,
3
+ ViewLoader,
4
+ ViewMatch,
5
+ } from '../types/view-matching'
6
+
7
+ /**
8
+ * Error thrown when view loading fails
9
+ */
10
+ export class ViewLoadingError extends Error {
11
+ public readonly loader: ViewLoader
12
+ public readonly config: FrameworkViewConfig
13
+ public override readonly cause?: Error
14
+
15
+ constructor(
16
+ message: string,
17
+ loader: ViewLoader,
18
+ config: FrameworkViewConfig,
19
+ cause?: Error
20
+ ) {
21
+ super(message)
22
+ this.name = 'ViewLoadingError'
23
+ this.loader = loader
24
+ this.config = config
25
+ this.cause = cause
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Framework-agnostic view loader that handles different loading strategies
31
+ */
32
+ export class UniversalViewLoader {
33
+ private cache = new Map<string, any>()
34
+
35
+ /**
36
+ * Load a component based on the framework configuration
37
+ */
38
+ async loadComponent(config: FrameworkViewConfig): Promise<any> {
39
+ const cacheKey = this.getCacheKey(config)
40
+
41
+ // Return cached component if available
42
+ if (this.cache.has(cacheKey)) {
43
+ return this.cache.get(cacheKey)
44
+ }
45
+
46
+ try {
47
+ const component = await this.loadByStrategy(config.loader)
48
+ this.cache.set(cacheKey, component)
49
+ return component
50
+ } catch (error) {
51
+ throw new ViewLoadingError(
52
+ `Failed to load component "${config.component}"`,
53
+ config.loader,
54
+ config,
55
+ error instanceof Error ? error : new Error(String(error))
56
+ )
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Load a component from a view match
62
+ */
63
+ async loadFromMatch(match: ViewMatch): Promise<any> {
64
+ if (!match.frameworkConfig) {
65
+ throw new ViewLoadingError(
66
+ `No framework config available for view "${match.view.id}"`,
67
+ { type: 'dynamic-import', path: '' },
68
+ {} as FrameworkViewConfig
69
+ )
70
+ }
71
+
72
+ return this.loadComponent(match.frameworkConfig)
73
+ }
74
+
75
+ /**
76
+ * Load component using the specific strategy
77
+ */
78
+ private async loadByStrategy(loader: ViewLoader): Promise<any> {
79
+ switch (loader.type) {
80
+ case 'dynamic-import':
81
+ return this.loadDynamicImport(loader.path)
82
+
83
+ case 'static-import':
84
+ return this.loadStaticImport(loader.path)
85
+
86
+ case 'custom-element':
87
+ return this.loadCustomElement(loader.tagName)
88
+
89
+ case 'registry-lookup':
90
+ return this.loadFromRegistry(loader.registryKey)
91
+
92
+ case 'custom':
93
+ return loader.loader({} as FrameworkViewConfig) // Will be passed proper config in real usage
94
+
95
+ default:
96
+ throw new Error(`Unknown loader type: ${(loader as any).type}`)
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Dynamic import strategy (most common)
102
+ * Supports both static strings and lazy-loaded callbacks
103
+ */
104
+ private async loadDynamicImport(
105
+ path: string | (() => Promise<any>)
106
+ ): Promise<any> {
107
+ // Handle callback-based lazy loading
108
+ if (typeof path === 'function') {
109
+ const module = await path()
110
+
111
+ // Handle different export patterns
112
+ if (module.default) {
113
+ return module.default
114
+ }
115
+
116
+ return module
117
+ }
118
+
119
+ // Handle string-based path (static)
120
+ const module = await import(path)
121
+
122
+ // Handle different export patterns
123
+ if (module.default) {
124
+ return module.default
125
+ }
126
+
127
+ // If no default export, return the entire module
128
+ return module
129
+ }
130
+
131
+ /**
132
+ * Static import strategy (requires bundler support)
133
+ * Supports both static strings and lazy-loaded callbacks
134
+ */
135
+ private async loadStaticImport(
136
+ path: string | (() => Promise<any>)
137
+ ): Promise<any> {
138
+ // Handle callback-based lazy loading
139
+ if (typeof path === 'function') {
140
+ const module = await path()
141
+
142
+ if (module.default) {
143
+ return module.default
144
+ }
145
+
146
+ return module
147
+ }
148
+
149
+ // This would typically be handled by bundler transformation
150
+ // For now, fall back to dynamic import
151
+ console.warn(
152
+ 'Static import not implemented, falling back to dynamic import'
153
+ )
154
+ return this.loadDynamicImport(path)
155
+ }
156
+
157
+ /**
158
+ * Custom element strategy (for Lit components)
159
+ */
160
+ private async loadCustomElement(tagName: string): Promise<any> {
161
+ // Wait for custom element to be defined
162
+ if (typeof window !== 'undefined' && window.customElements) {
163
+ await window.customElements.whenDefined(tagName)
164
+ return { tagName, type: 'custom-element' }
165
+ }
166
+
167
+ throw new Error('Custom elements not available in this environment')
168
+ }
169
+
170
+ /**
171
+ * Registry lookup strategy (for pre-registered components)
172
+ */
173
+ private async loadFromRegistry(registryKey: string): Promise<any> {
174
+ // This would look up in a component registry
175
+ // Implementation depends on framework
176
+ throw new Error(`Registry lookup not implemented for key: ${registryKey}`)
177
+ }
178
+
179
+ /**
180
+ * Generate cache key for component config
181
+ */
182
+ private getCacheKey(config: FrameworkViewConfig): string {
183
+ const { loader, component, package: pkg } = config
184
+ return JSON.stringify({ loader, component, package: pkg })
185
+ }
186
+
187
+ /**
188
+ * Clear the component cache
189
+ */
190
+ clearCache(): void {
191
+ this.cache.clear()
192
+ }
193
+
194
+ /**
195
+ * Get cache statistics
196
+ */
197
+ getCacheStats(): { size: number; keys: string[] } {
198
+ return {
199
+ size: this.cache.size,
200
+ keys: Array.from(this.cache.keys()),
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Singleton loader instance
207
+ */
208
+ let globalLoader: UniversalViewLoader | null = null
209
+
210
+ /**
211
+ * Get the global view loader
212
+ */
213
+ export function getGlobalLoader(): UniversalViewLoader {
214
+ if (!globalLoader) {
215
+ globalLoader = new UniversalViewLoader()
216
+ }
217
+ return globalLoader
218
+ }
219
+
220
+ /**
221
+ * Set a custom global loader (useful for testing)
222
+ */
223
+ export function setGlobalLoader(loader: UniversalViewLoader): void {
224
+ globalLoader = loader
225
+ }
226
+
227
+ /**
228
+ * Framework-specific helpers for strategically optimized loading patterns
229
+ *
230
+ * Following our Suffering Economics optimization, we support:
231
+ * - Lit Web Components (universal web compatibility) - REQUIRED
232
+ * - Vue (native Vue optimization) - OPTIONAL
233
+ * - React Native (mobile-native optimization) - OPTIONAL (WebView fallback available)
234
+ *
235
+ * All loaders support lazy loading via callbacks:
236
+ * ```typescript
237
+ * // Static (bundled)
238
+ * vueComponent('./Component.vue')
239
+ *
240
+ * // Lazy (loaded on demand)
241
+ * vueComponent(() => import('./Component.vue'))
242
+ * ```
243
+ */
244
+ export const LoaderHelpers = {
245
+ /**
246
+ * Create a Lit custom element loader (universal web compatibility)
247
+ */
248
+ litComponent(tagName: string): ViewLoader {
249
+ return { type: 'custom-element', tagName }
250
+ },
251
+
252
+ /**
253
+ * Create a Vue component loader (native Vue, optional)
254
+ * Supports both static paths and lazy-loaded callbacks
255
+ */
256
+ vueComponent(path: string | (() => Promise<any>)): ViewLoader {
257
+ return { type: 'dynamic-import', path }
258
+ },
259
+
260
+ /**
261
+ * Create a React Native JSX dynamic import loader (mobile-native, optional)
262
+ * Supports both static paths and lazy-loaded callbacks
263
+ */
264
+ rnComponent(path: string | (() => Promise<any>)): ViewLoader {
265
+ return { type: 'dynamic-import', path }
266
+ },
267
+
268
+ /**
269
+ * Create a custom loader function for advanced use cases
270
+ */
271
+ custom(loader: (config: FrameworkViewConfig) => Promise<any>): ViewLoader {
272
+ return { type: 'custom', loader }
273
+ },
274
+
275
+ // Legacy helpers for compilation pipelines (Vue/React → Lit)
276
+ /**
277
+ * @deprecated Use litComponent() instead. This exists for compilation pipeline compatibility.
278
+ */
279
+ vueToLitCompiled(tagName: string): ViewLoader {
280
+ console.warn(
281
+ 'vueToLitCompiled is deprecated. Use litComponent() - the output is the same.'
282
+ )
283
+ return { type: 'custom-element', tagName }
284
+ },
285
+
286
+ /**
287
+ * @deprecated Use litComponent() instead. This exists for compilation pipeline compatibility.
288
+ */
289
+ reactToLitCompiled(tagName: string): ViewLoader {
290
+ console.warn(
291
+ 'reactToLitCompiled is deprecated. Use litComponent() - the output is the same.'
292
+ )
293
+ return { type: 'custom-element', tagName }
294
+ },
295
+
296
+ /**
297
+ * @deprecated Use rnComponent() instead. Updated for better TypeScript support.
298
+ */
299
+ reactNativeComponent(path: string): ViewLoader {
300
+ console.warn(
301
+ 'reactNativeComponent is deprecated. Use rnComponent() instead.'
302
+ )
303
+ return { type: 'dynamic-import', path }
304
+ },
305
+ }
@@ -0,0 +1,135 @@
1
+ import type { DecoderResult } from '@lukso/transaction-decoder'
2
+ import type {
3
+ ViewDefinition,
4
+ ViewMatch,
5
+ ViewMatchOptions,
6
+ ViewRegistry,
7
+ } from '../types/view-matching'
8
+ import { findMatchingViews } from '../utils/view-matcher'
9
+
10
+ /**
11
+ * Default view registry implementation
12
+ */
13
+ export class DefaultViewRegistry implements ViewRegistry {
14
+ private views = new Map<string, ViewDefinition>()
15
+
16
+ register(view: ViewDefinition): void {
17
+ if (this.views.has(view.id)) {
18
+ console.warn(
19
+ `View with id "${view.id}" is already registered. Overwriting.`
20
+ )
21
+ }
22
+
23
+ this.views.set(view.id, view)
24
+ }
25
+
26
+ unregister(viewId: string): void {
27
+ this.views.delete(viewId)
28
+ }
29
+
30
+ getAllViews(): ViewDefinition[] {
31
+ return Array.from(this.views.values())
32
+ }
33
+
34
+ findMatches(
35
+ transaction: DecoderResult,
36
+ options: ViewMatchOptions
37
+ ): ViewMatch[] {
38
+ const allViews = this.getAllViews()
39
+ return findMatchingViews(transaction, allViews, options)
40
+ }
41
+
42
+ getView(viewId: string): ViewDefinition | undefined {
43
+ return this.views.get(viewId)
44
+ }
45
+
46
+ clear(): void {
47
+ this.views.clear()
48
+ }
49
+
50
+ /**
51
+ * Get the best matching view (highest score) for a transaction
52
+ */
53
+ getBestMatch(
54
+ transaction: DecoderResult,
55
+ options: ViewMatchOptions
56
+ ): ViewMatch | null {
57
+ const matches = this.findMatches(transaction, { ...options, maxMatches: 1 })
58
+ return matches[0] || null
59
+ }
60
+
61
+ /**
62
+ * Bulk register multiple views
63
+ */
64
+ registerMany(views: ViewDefinition[]): void {
65
+ for (const view of views) {
66
+ this.register(view)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get stats about registered views
72
+ */
73
+ getStats(): {
74
+ totalViews: number
75
+ viewsByFramework: Record<string, number>
76
+ viewsByRecordType: Record<string, number>
77
+ } {
78
+ const allViews = this.getAllViews()
79
+ const totalViews = allViews.length
80
+
81
+ const viewsByFramework: Record<string, number> = {}
82
+ const viewsByRecordType: Record<string, number> = {}
83
+
84
+ for (const view of allViews) {
85
+ // Count framework support
86
+ for (const framework of Object.keys(view.frameworks)) {
87
+ viewsByFramework[framework] = (viewsByFramework[framework] || 0) + 1
88
+ }
89
+
90
+ // Count by record type
91
+ const recordType = view.criteria.recordType
92
+ if (recordType) {
93
+ const types = Array.isArray(recordType) ? recordType : [recordType]
94
+ for (const type of types) {
95
+ viewsByRecordType[type] = (viewsByRecordType[type] || 0) + 1
96
+ }
97
+ }
98
+ }
99
+
100
+ return {
101
+ totalViews,
102
+ viewsByFramework,
103
+ viewsByRecordType,
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Global registry instance
110
+ */
111
+ let globalRegistry: ViewRegistry | null = null
112
+
113
+ /**
114
+ * Get or create the global view registry
115
+ */
116
+ export function getGlobalRegistry(): ViewRegistry {
117
+ if (!globalRegistry) {
118
+ globalRegistry = new DefaultViewRegistry()
119
+ }
120
+ return globalRegistry
121
+ }
122
+
123
+ /**
124
+ * Set a custom global registry (useful for testing or custom implementations)
125
+ */
126
+ export function setGlobalRegistry(registry: ViewRegistry): void {
127
+ globalRegistry = registry
128
+ }
129
+
130
+ /**
131
+ * Reset the global registry (mainly for testing)
132
+ */
133
+ export function resetGlobalRegistry(): void {
134
+ globalRegistry = null
135
+ }
@@ -0,0 +1,16 @@
1
+ export type {
2
+ ViewContext,
3
+ ViewContextRequirements,
4
+ ViewVariant,
5
+ } from './view-contexts'
6
+ export type {
7
+ DecoderRecordForSelection,
8
+ FrameworkSupport,
9
+ FrameworkViewConfig,
10
+ ViewDefinition,
11
+ ViewLoader,
12
+ ViewMatch,
13
+ ViewMatchCriteria,
14
+ ViewMatchOptions,
15
+ ViewRegistry,
16
+ } from './view-matching'
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Different contexts WHERE transaction views are displayed
3
+ *
4
+ * Context determines the environment/location, which affects:
5
+ * - Available space
6
+ * - User intent
7
+ * - Security requirements
8
+ * - Interaction patterns
9
+ */
10
+ export type ViewContext =
11
+ | 'approval' // Transaction approval flow - security focused
12
+ | 'feed' // Activity feed - compact summaries
13
+ | 'list' // Transaction lists - scannable format
14
+ | 'detail' // Full transaction detail - comprehensive
15
+ | 'review' // Pre-submission review - verification focused
16
+ | 'notification' // Push notifications - minimal info
17
+ | 'embed' // Embedded in other interfaces
18
+ | 'batch-summary' // Inside a batch summary display
19
+
20
+ /**
21
+ * Different variants of HOW transaction views are displayed
22
+ *
23
+ * Variant determines the presentation style within a context:
24
+ * - Same transaction can have different variants in different contexts
25
+ * - Example: 'collapsed' in feed vs 'collapsed' in approval are different
26
+ */
27
+ export type ViewVariant =
28
+ | 'full' // Complete detailed view
29
+ | 'collapsed' // Collapsed/summary view (context-dependent)
30
+ | 'minimal' // Minimal information only
31
+ | 'icon-only' // Just icon/avatar
32
+ | 'compact' // Compact single-line format
33
+ | 'expanded' // Expanded with all details
34
+ | string // Custom variants allowed
35
+
36
+ /**
37
+ * Context-specific display requirements
38
+ */
39
+ export interface ViewContextRequirements {
40
+ /** Maximum height constraint */
41
+ maxHeight?: number
42
+ /** Whether to show action buttons */
43
+ showActions?: boolean
44
+ /** Level of detail to display */
45
+ detailLevel: 'minimal' | 'summary' | 'full' | 'debug'
46
+ /** Whether user can expand for more details */
47
+ expandable?: boolean
48
+ /** Color scheme preference */
49
+ colorScheme?: 'light' | 'dark' | 'auto'
50
+ /** Whether to emphasize security warnings */
51
+ emphasizeSecurity?: boolean
52
+ }
53
+
54
+ /**
55
+ * Extended view matching criteria with context and variant support
56
+ */
57
+ export interface ContextAwareViewCriteria {
58
+ /** Standard matching criteria */
59
+ recordType?: string | string[]
60
+ functionName?: string | string[]
61
+ standard?: string | string[]
62
+ contractAddress?: string | string[]
63
+ chainId?: number | number[]
64
+ customMatcher?: (transaction: any) => boolean
65
+
66
+ /** Context-specific matching (WHERE to display) */
67
+ context?: ViewContext | ViewContext[]
68
+ /** Variant-specific matching (HOW to display) */
69
+ variant?: ViewVariant | ViewVariant[]
70
+ /** Specific context requirements */
71
+ contextRequirements?: Partial<ViewContextRequirements>
72
+ }
@@ -0,0 +1,173 @@
1
+ import type { DecoderResult } from '@lukso/transaction-decoder'
2
+
3
+ // Type for view selection with all possible decoder properties as optional
4
+ // The decoder conditionally adds these based on transaction type
5
+ export type DecoderRecordForSelection = DecoderResult & {
6
+ recordType?: string
7
+ functionName?: string
8
+ contractAddress?: string
9
+ standard?: string
10
+ }
11
+
12
+ import type {
13
+ ViewContext,
14
+ ViewContextRequirements,
15
+ ViewVariant,
16
+ } from './view-contexts'
17
+
18
+ /**
19
+ * Criteria that can be used to match transactions to views
20
+ */
21
+ export interface ViewMatchCriteria {
22
+ /** LSP standard (e.g., 'LSP7', 'LSP8', 'LSP0') */
23
+ standard?: string | string[]
24
+ /** Function name from ABI (e.g., 'transfer', 'setData', 'execute') */
25
+ functionName?: string | string[]
26
+ /** Record type from decoder (e.g., 'lsp7-transfer', 'set-data') */
27
+ recordType?: string | string[]
28
+ /** Contract address (for specific contract implementations) */
29
+ contractAddress?: string | string[]
30
+ /** Chain ID (for network-specific views) */
31
+ chainId?: number | number[]
32
+ /** Display context (opt-in) - WHERE to display (feed, approval, list, etc.) */
33
+ context?: ViewContext | ViewContext[]
34
+ /** Display variant (opt-in) - HOW to display (collapsed, minimal, full, etc.) */
35
+ variant?: ViewVariant | ViewVariant[]
36
+ /** Context requirements (opt-in) - additional constraints for context matching */
37
+ contextRequirements?: Partial<ViewContextRequirements>
38
+ /**
39
+ * Custom matcher function for complex synchronous logic.
40
+ * Returns the number of conditions that matched (higher = better match).
41
+ *
42
+ * IMPORTANT: Must be synchronous and only use data from DecoderResult.
43
+ * For async operations, populate transaction.custom in an enhancer.
44
+ *
45
+ * @returns Number of matching conditions (0 = no match, higher = stronger match)
46
+ *
47
+ * @example
48
+ * // Match token transfers (tokenType === 0)
49
+ * customMatcher: (tx) => {
50
+ * let matches = 0
51
+ * if (tx.custom?.tokenType === 0) matches++
52
+ * if (BigInt(tx.value || 0) > 0) matches++
53
+ * return matches
54
+ * }
55
+ */
56
+ customMatcher?: (transaction: DecoderResult) => number
57
+ }
58
+
59
+ /**
60
+ * Framework-agnostic view definition
61
+ */
62
+ export interface ViewDefinition {
63
+ /** Unique identifier for this view */
64
+ id: string
65
+ /** Human-readable name */
66
+ name: string
67
+ /** Criteria for matching transactions to this view */
68
+ criteria: ViewMatchCriteria
69
+ /** Priority when multiple views match (higher = preferred) */
70
+ priority: number
71
+ /** Supported frameworks for this view */
72
+ frameworks: FrameworkSupport
73
+ /** Optional description */
74
+ description?: string
75
+ }
76
+
77
+ /**
78
+ * Framework-specific loading configurations
79
+ *
80
+ * Strategic Framework Selection (Suffering Economics Optimized):
81
+ * - lit: Universal web components (works in Vue, React, Angular, Svelte, etc.)
82
+ * - vue: Native Vue components (optional, for Vue-specific optimizations)
83
+ * - rn: React Native components (mobile-native, optional - falls back to WebView)
84
+ *
85
+ * Lit is the universal fallback. Vue and React Native are optional native implementations
86
+ * that get priority when available in their respective environments.
87
+ */
88
+ export interface FrameworkSupport {
89
+ lit: FrameworkViewConfig // Web Components (universal web compatibility) - REQUIRED
90
+ vue?: FrameworkViewConfig // Vue 3 components (native Vue, optional)
91
+ rn?: FrameworkViewConfig // React Native JSX (mobile-native, optional)
92
+ }
93
+
94
+ /**
95
+ * Configuration for loading a view in a specific framework
96
+ */
97
+ export interface FrameworkViewConfig {
98
+ /** Loading method */
99
+ loader: ViewLoader
100
+ /** Component name/identifier */
101
+ component: string
102
+ /** Package where the component is located */
103
+ package?: string
104
+ }
105
+
106
+ /**
107
+ * Different ways to load a view component
108
+ *
109
+ * Loaders support both static and lazy loading:
110
+ * - Static: `path: './Component.vue'` - bundled with app
111
+ * - Lazy: `path: () => import('./Component.vue')` - loaded on demand
112
+ */
113
+ export type ViewLoader =
114
+ | { type: 'dynamic-import'; path: string | (() => Promise<any>) }
115
+ | { type: 'static-import'; path: string | (() => Promise<any>) }
116
+ | { type: 'custom-element'; tagName: string }
117
+ | { type: 'registry-lookup'; registryKey: string }
118
+ | { type: 'custom'; loader: (config: FrameworkViewConfig) => Promise<any> }
119
+
120
+ /**
121
+ * Result of matching a transaction against registered views
122
+ */
123
+ export interface ViewMatch {
124
+ /** The view definition that matched */
125
+ view: ViewDefinition
126
+ /** Score indicating how well this view matches (0-1) */
127
+ score: number
128
+ /** Which criteria matched */
129
+ matchedCriteria: (keyof ViewMatchCriteria)[]
130
+ /** Framework-specific loading config */
131
+ frameworkConfig?: FrameworkViewConfig
132
+ }
133
+
134
+ /**
135
+ * Options for view matching
136
+ */
137
+ export interface ViewMatchOptions {
138
+ /** Target framework */
139
+ framework: 'lit' | 'vue' | 'rn'
140
+ /** Display context (optional) - WHERE to display (feed, approval, list, etc.) */
141
+ context?: ViewContext
142
+ /** Display variant (optional) - HOW to display (collapsed, minimal, full, etc.) */
143
+ variant?: ViewVariant
144
+ /** Context requirements (optional) - additional constraints for context-aware matching */
145
+ contextRequirements?: Partial<ViewContextRequirements>
146
+ /** Minimum score threshold (0-1) */
147
+ minScore?: number
148
+ /** Maximum number of matches to return */
149
+ maxMatches?: number
150
+ /** Whether to include framework config in results */
151
+ includeFrameworkConfig?: boolean
152
+ }
153
+
154
+ /**
155
+ * Registry for managing view definitions
156
+ */
157
+ export interface ViewRegistry {
158
+ /** Register a new view */
159
+ register(view: ViewDefinition): void
160
+ /** Unregister a view by ID */
161
+ unregister(viewId: string): void
162
+ /** Get all registered views */
163
+ getAllViews(): ViewDefinition[]
164
+ /** Find best matching views for a transaction */
165
+ findMatches(
166
+ transaction: DecoderResult,
167
+ options: ViewMatchOptions
168
+ ): ViewMatch[]
169
+ /** Get a specific view by ID */
170
+ getView(viewId: string): ViewDefinition | undefined
171
+ /** Clear all registered views */
172
+ clear(): void
173
+ }