|
1 | | -### Countdown Monetization Plan (Gumroad One‑Time License, Client‑Only) |
| 1 | +### Super Tidy Pro: Countdown Monetization System |
2 | 2 |
|
3 | | -#### Goal |
4 | | -- Introduce a countdown gate (random 6–15s) before executing any command for unlicensed users, with an in-UI option to purchase and skip the delay. |
5 | | -- Licensed customers experience zero delay. |
6 | | -- Payments handled via a Gumroad product (one-time); license activation is client-only via Gumroad License Verification API with Netlify Function proxy for secure API operations. |
| 3 | +#### Overview |
| 4 | +Implement a premium licensing system with countdown gates for free users and instant execution for licensed users. The system uses Gumroad for payments and license management, with a client-side verification approach optimized for performance and user experience. |
| 5 | + |
| 6 | +#### Product Goals |
| 7 | +- **Premium Experience**: Licensed users get instant command execution with zero friction |
| 8 | +- **Conversion Optimization**: Free users experience 6-15 second countdown with seamless upgrade path |
| 9 | +- **Reliable Architecture**: Robust data flow with comprehensive error tracking and observability |
| 10 | +- **Scalable Foundation**: Clean separation of concerns and reusable architectural patterns |
7 | 11 |
|
8 | 12 | --- |
9 | 13 |
|
|
36 | 40 |
|
37 | 41 | --- |
38 | 42 |
|
39 | | -### Technical Architecture (Final Implementation) |
| 43 | +### Technical Architecture |
40 | 44 |
|
41 | | -#### **Core Architecture Principles** |
42 | | -- **Embedded Countdown**: Countdown is a state within FormView.js, not a separate route |
43 | | -- **Clean Separation**: UI logic in UI layer, Figma APIs in Core.js |
44 | | -- **Direct Communication**: UI components use direct method calls, postMessage only for Core.js communication |
45 | | -- **License Caching**: License status cached in memory for fast gate decisions |
| 45 | +#### **Design Principles** |
| 46 | +- **Unidirectional Data Flow**: Data flows from Core → App → Components via props |
| 47 | +- **Separation of Concerns**: Core.js handles Figma APIs, UI handles presentation logic |
| 48 | +- **Centralized Storage**: Single utility manages all clientStorage operations with validation |
| 49 | +- **Observability First**: Comprehensive error tracking and analytics integration |
| 50 | +- **Memory Efficiency**: Proper cleanup patterns and singleton utilities |
46 | 51 |
|
47 | 52 | #### **Component Responsibilities** |
48 | 53 |
|
|
54 | 59 | - Supports both UI-initiated and direct (menu) countdowns via callback parameter |
55 | 60 | - Imports and displays AnalogChronometer component |
56 | 61 |
|
57 | | -**Core.js** (Pure Figma APIs): |
58 | | -- Handles Figma Canvas and Storage APIs only |
59 | | -- Manages license storage in `figma.clientStorage` (`LICENSE_V1` key) |
60 | | -- Wraps menu commands with `ensureDirectCommandGate` for gating |
61 | | -- Sends `start-direct-countdown` messages to UI for menu-initiated commands |
62 | | -- Processes license-related messages: `get-license`, `activate-license`, `remove-license` |
63 | | - |
64 | | -**App.js** (Minimal orchestrator): |
65 | | -- Handles Core.js messages and initializes views |
66 | | -- `handleDirectCountdown` navigates to FormView and calls startCountdown directly |
67 | | -- Router setup excludes countdown route (no longer needed) |
68 | | -- Removed CountdownView import and rendering from App.js |
69 | | -- Direct integration with FormView's embedded countdown functionality |
70 | | - |
71 | | -**LicenseView.js** (License management): |
72 | | -- Dual-state rendering: unlicensed form vs licensed info display |
73 | | -- Gumroad API integration via Netlify Function proxy |
74 | | -- 2-device usage limit enforcement |
75 | | -- License validation, activation, and unlinking |
76 | | -- Device usage tracking and display |
| 62 | +**Core.js** (Figma API Layer): |
| 63 | +- Manages all Figma Canvas and Storage APIs exclusively |
| 64 | +- Handles license storage using centralized Storage utility |
| 65 | +- Implements command gating with `ensureDirectCommandGate` |
| 66 | +- Includes license data in all initialization messages |
| 67 | +- Processes license activation/removal via postMessage |
| 68 | + |
| 69 | +**App.js** (Data Orchestration): |
| 70 | +- Receives initialization data from Core.js including license status |
| 71 | +- Stores license and preferences in component state |
| 72 | +- Passes data to child components via HTML attributes |
| 73 | +- Handles direct countdown coordination for menu commands |
| 74 | +- Manages view routing and component lifecycle |
| 75 | + |
| 76 | +**FormView.js** (Primary UI & Gating): |
| 77 | +- Embedded countdown state within main form interface |
| 78 | +- Receives license data via props from App.js |
| 79 | +- Manages countdown timer and analog chronometer display |
| 80 | +- Handles both UI-initiated and menu-triggered countdowns |
| 81 | +- Updates license cache directly from props |
| 82 | + |
| 83 | +**LicenseView.js** (License Management): |
| 84 | +- Dual-state rendering: activation form vs license info display |
| 85 | +- Receives current license data via props from App.js |
| 86 | +- Integrates with Gumroad API for license verification |
| 87 | +- Manages license activation and device unlinking |
| 88 | +- Provides user support and troubleshooting access |
77 | 89 |
|
78 | 90 | **AnalogChronometer.js** (Visual countdown): |
79 | 91 | - Animated analog clock with red needle |
80 | 92 | - Dynamic tick generation based on total seconds |
81 | 93 | - Attribute-based updates (`total-seconds`, `current-seconds`) |
82 | 94 | - Clean white circle design with red accents |
83 | 95 |
|
84 | | -#### **Gating Flow** |
85 | | -1. User triggers command (UI form or menu) |
86 | | -2. Check cached license status via `shouldShowCountdown()` |
87 | | -3. **If licensed**: Execute command immediately |
88 | | -4. **If unlicensed**: |
89 | | - - FormView shows countdown state (embedded, not separate route) |
90 | | - - User stays on "Actions" tab throughout countdown |
91 | | - - After countdown: "Run Now" button enables |
92 | | - - User clicks "Run Now" to execute command |
93 | | - |
94 | | -#### **Menu Command Gating** |
95 | | -- Core.js wraps menu commands with `ensureDirectCommandGate` |
96 | | -- Shows UI and sends `start-direct-countdown` message |
97 | | -- App.js calls FormView.startCountdown with callback |
98 | | -- Callback sends `direct-countdown-complete` message back to Core.js |
99 | | -- Licensed users get `figma.closePlugin()` immediately |
| 96 | +#### **Storage Architecture** |
| 97 | + |
| 98 | +**Centralized Storage Utility** (`src/utils/Storage.js`): |
| 99 | +- Constructor-based singleton pattern initialized from Core.js |
| 100 | +- Map-based key validation prevents runtime errors |
| 101 | +- Automatic error tracking for all storage operations |
| 102 | +- Promise-based API using then/catch for compatibility |
| 103 | +- Batch operations (getMultiple/setMultiple) for efficiency |
| 104 | + |
| 105 | +**Storage Key Management**: |
| 106 | +```javascript |
| 107 | +// Defined in Core.js |
| 108 | +const STORAGE_KEYS = { |
| 109 | + UUID: 'UUID', |
| 110 | + PREFERENCES: 'preferences', |
| 111 | + LICENSE_V1: 'LICENSE_V1', |
| 112 | + AD_LAST_SHOWN_DATE: 'AD_LAST_SHOWN_DATE', |
| 113 | + AD_LAST_SHOWN_IMPRESSION: 'AD_LAST_SHOWN_IMPRESSION' |
| 114 | +} |
| 115 | + |
| 116 | +// Usage throughout application |
| 117 | +Storage.get(Storage.getKey('LICENSE_V1')) |
| 118 | +Storage.set(Storage.getKey('UUID'), value) |
| 119 | +``` |
| 120 | + |
| 121 | +**Error Tracking Integration**: |
| 122 | +All storage failures automatically emit tracking events: |
| 123 | +```javascript |
| 124 | +{ |
| 125 | + type: 'tracking-event', |
| 126 | + event: 'storage-operation-failed', |
| 127 | + properties: { |
| 128 | + operation: 'get|set|remove', |
| 129 | + key: 'LICENSE_V1', |
| 130 | + error: 'Detailed error message', |
| 131 | + timestamp: Date.now() |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +#### **Data Flow Architecture** |
| 137 | + |
| 138 | +**Initialization Flow**: |
| 139 | +``` |
| 140 | +Core.js → Storage.getMultiple() → Include in init messages → App.js → Props → Components |
| 141 | +``` |
| 142 | + |
| 143 | +**License Activation Flow**: |
| 144 | +``` |
| 145 | +LicenseView → Gumroad API → activateLicense() → Core.js → Storage.set() → Cache Update |
| 146 | +``` |
| 147 | + |
| 148 | +**Command Execution Flow**: |
| 149 | +``` |
| 150 | +User Action → Gate Check → [Licensed: Execute] | [Unlicensed: Countdown → Manual Execute] |
| 151 | +``` |
| 152 | + |
| 153 | +#### **Gating Implementation** |
| 154 | +1. **User triggers command** (UI form or menu) |
| 155 | +2. **License check** via cached status in `shouldShowCountdown()` |
| 156 | +3. **Licensed path**: Execute command immediately |
| 157 | +4. **Unlicensed path**: |
| 158 | + - FormView shows countdown state (embedded in current view) |
| 159 | + - User remains on current tab throughout countdown |
| 160 | + - After countdown completion: "Run Now" button enables |
| 161 | + - Manual execution trigger required |
| 162 | + |
| 163 | +#### **Menu Command Integration** |
| 164 | +- Core.js wraps all menu commands with `ensureDirectCommandGate` |
| 165 | +- Shows UI and includes license data in `init-direct` message |
| 166 | +- App.js coordinates FormView countdown with execution callback |
| 167 | +- Licensed users bypass countdown entirely with immediate execution |
100 | 168 |
|
101 | 169 | --- |
102 | 170 |
|
|
207 | 275 | 1. **Race Conditions**: FormView event listeners were accumulating without cleanup |
208 | 276 | 2. **Data Consistency**: Usage count not properly stored during license activation |
209 | 277 | 3. **Silent Failures**: Storage operations lacked error handling and user feedback |
210 | | -4. **Solution**: Implemented proper event listener cleanup, comprehensive error handling, and consistent data format |
| 278 | +4. **Complex Timeout Patterns**: Duplicated postMessage request/response cycles with timeout cleanup |
| 279 | +5. **Memory Leaks**: Event listeners not properly cleaned up in license retrieval |
| 280 | +6. **Solution**: Implemented proper event listener cleanup, comprehensive error handling, consistent data format, and eliminated postMessage complexity |
211 | 281 |
|
212 | 282 | #### **Key Technical Decisions** |
213 | 283 | - **Memory over Storage**: License status cached in `gate.js` for performance |
|
216 | 286 | - **State Management**: LEO Element data properties for countdown state |
217 | 287 | - **Event Listener Management**: Proper cleanup to prevent memory leaks and race conditions |
218 | 288 | - **Error Handling**: Comprehensive error handling for all storage operations with user feedback |
| 289 | +- **Centralized Storage**: Storage utility with automatic error tracking and validation |
| 290 | +- **Data Down, Events Up**: License data flows from Core → App → Components via props |
| 291 | +- **Unidirectional Data Flow**: Eliminated complex postMessage request/response cycles |
219 | 292 |
|
220 | 293 | --- |
221 | 294 |
|
|
228 | 301 | - [x] Manual "Run Now" button after countdown completion |
229 | 302 | - [x] License tab with dual-state rendering |
230 | 303 | - [x] Gumroad license verification and activation |
231 | | -- [x] 2-device usage limit with Netlify Function proxy |
232 | | -- [x] License info display and device usage tracking |
| 304 | +- [x] ~~2-device usage limit with Netlify Function proxy~~ (Temporarily disabled for easier management) |
| 305 | +- [x] License info display ~~and device usage tracking~~ (Device tracking temporarily hidden) |
233 | 306 | - [x] License unlinking with usage count decrement |
234 | 307 | - [x] Menu command gating for direct Figma menu access |
235 | 308 | - [x] License status caching for fast gate decisions |
|
238 | 311 | - [x] Fixed license storage/retrieval race conditions and reliability issues |
239 | 312 | - [x] Comprehensive error handling with user feedback for all storage operations |
240 | 313 | - [x] Proper event listener cleanup to prevent memory leaks |
| 314 | +- [x] **NEW**: Centralized Storage utility with automatic error tracking |
| 315 | +- [x] **NEW**: Eliminated postMessage timeout patterns for license retrieval |
| 316 | +- [x] **NEW**: Unidirectional data flow (Core → App → Components) |
| 317 | +- [x] **NEW**: Comprehensive storage operation tracking and analytics |
241 | 318 |
|
242 | 319 | #### **✅ Tested Scenarios** |
243 | 320 | - [x] Licensed user: immediate command execution |
244 | 321 | - [x] Unlicensed user: countdown → manual execution |
245 | 322 | - [x] License activation: Gumroad API integration |
246 | 323 | - [x] Menu commands: gating works from Figma menu |
247 | | -- [x] Device limits: 2-device enforcement |
| 324 | +- [x] ~~Device limits: 2-device enforcement~~ (Temporarily disabled) |
248 | 325 | - [x] License unlinking: usage count decremented |
249 | 326 | - [x] Error handling: invalid keys, API failures |
250 | 327 | - [x] License storage reliability: consistent save/load across plugin sessions |
251 | 328 | - [x] Memory management: no event listener leaks or race conditions |
252 | 329 | - [x] Error recovery: proper user feedback for storage failures |
| 330 | +- [x] **NEW**: Storage error tracking and analytics integration |
| 331 | +- [x] **NEW**: License data propagation through init messages |
| 332 | +- [x] **NEW**: Props-based license data flow without postMessage complexity |
253 | 333 |
|
254 | 334 | #### **🎯 Production Ready** |
255 | 335 | The countdown monetization system is fully implemented and functional, with: |
@@ -287,22 +367,124 @@ The countdown monetization system is fully implemented and functional, with: |
287 | 367 |
|
288 | 368 | --- |
289 | 369 |
|
290 | | -### Future Enhancements (Optional) |
291 | | - |
292 | | -#### **Potential Improvements** |
293 | | -- [ ] Server-side license validation for stronger security |
294 | | -- [ ] Usage analytics and conversion optimization |
295 | | -- [ ] License transfer between devices |
296 | | -- [ ] Bulk license management for teams |
297 | | -- [ ] Additional license tiers or features |
298 | | - |
299 | | -#### **Technical Debt** |
300 | | -- [ ] Migrate from LEO.js to modern framework (if needed) |
301 | | -- [x] Enhanced error handling and retry mechanisms (COMPLETED) |
302 | | -- [ ] Automated testing for license flows |
303 | | -- [ ] Performance optimization for large selections |
304 | | -- [x] Memory leak prevention and proper cleanup (COMPLETED) |
305 | | -- [x] Race condition fixes in license management (COMPLETED) |
| 370 | +### Development Best Practices |
| 371 | + |
| 372 | +#### **Storage Operations** |
| 373 | +- Always use centralized Storage utility with key validation |
| 374 | +- Handle both success and failure cases with user feedback |
| 375 | +- Use batch operations for related data updates |
| 376 | +- Initialize all keys in Core.js, avoid hardcoded strings |
| 377 | + |
| 378 | +#### **Data Flow Patterns** |
| 379 | +- Prefer "data down, events up" architecture |
| 380 | +- Include data in initialization messages rather than separate requests |
| 381 | +- Use component props/attributes instead of complex postMessage cycles |
| 382 | +- Maintain single source of truth for application state |
| 383 | + |
| 384 | +#### **Error Handling & Observability** |
| 385 | +- Emit tracking events for all error conditions |
| 386 | +- Provide specific, actionable error messages to users |
| 387 | +- Log contextual information for effective debugging |
| 388 | +- Distinguish between recoverable and fatal error types |
| 389 | + |
| 390 | +#### **Memory & Performance** |
| 391 | +- Clean up event listeners in component lifecycle methods |
| 392 | +- Use singleton patterns to prevent resource duplication |
| 393 | +- Prefer direct method calls over complex async coordination |
| 394 | +- Implement proper component cleanup patterns |
| 395 | + |
| 396 | +#### **Code Organization** |
| 397 | +- Separate Figma API operations from UI logic |
| 398 | +- Create reusable utilities for common patterns |
| 399 | +- Use constructor-based classes for stateful services |
| 400 | +- Implement comprehensive input validation with clear error messages |
| 401 | + |
| 402 | +--- |
| 403 | + |
| 404 | +### Implementation Phases |
| 405 | + |
| 406 | +#### **Phase 1: Core Infrastructure** |
| 407 | +- Centralized Storage utility implementation with key validation |
| 408 | +- Basic countdown timer and gating logic |
| 409 | +- License data flow architecture via props |
| 410 | +- Error tracking and analytics integration |
| 411 | + |
| 412 | +#### **Phase 2: Gumroad Integration** |
| 413 | +- License verification API integration |
| 414 | +- Secure key management and hashing |
| 415 | +- Purchase flow and activation process |
| 416 | +- Device management capabilities |
| 417 | + |
| 418 | +#### **Phase 3: User Experience Polish** |
| 419 | +- Analog chronometer animation implementation |
| 420 | +- Visual design and interaction refinement |
| 421 | +- Comprehensive error handling and user messaging |
| 422 | +- Performance optimization and testing |
| 423 | + |
| 424 | +#### **Phase 4: Analytics & Optimization** |
| 425 | +- Conversion funnel implementation |
| 426 | +- A/B testing framework setup |
| 427 | +- Performance monitoring and alerting |
| 428 | +- User feedback collection and analysis |
| 429 | + |
| 430 | +### Testing Strategy |
| 431 | + |
| 432 | +#### **Automated Testing** |
| 433 | +- Unit tests for Storage utility and core functions |
| 434 | +- Integration tests for license verification flow |
| 435 | +- Performance tests for countdown and activation timing |
| 436 | +- Error scenario testing for network and storage failures |
| 437 | + |
| 438 | +#### **Manual Testing Scenarios** |
| 439 | +- Licensed user: instant command execution across all entry points |
| 440 | +- Unlicensed user: countdown completion and manual execution |
| 441 | +- License activation: Gumroad integration and key validation |
| 442 | +- Menu commands: gating functionality from Figma menu |
| 443 | +- Error recovery: invalid keys, network failures, storage issues |
| 444 | + |
| 445 | +#### **User Acceptance Testing** |
| 446 | +- Conversion flow optimization and usability |
| 447 | +- User interface accessibility and cross-platform compatibility |
| 448 | +- Performance under various system conditions |
| 449 | +- Edge case handling and error recovery |
| 450 | + |
| 451 | +--- |
| 452 | + |
| 453 | +### Future Enhancements |
| 454 | + |
| 455 | +#### **Advanced Features** |
| 456 | +- Server-side license validation for enhanced security |
| 457 | +- Team license management and bulk operations |
| 458 | +- License transfer capabilities between devices |
| 459 | +- Advanced usage analytics and reporting |
| 460 | +- Multiple license tiers and feature sets |
| 461 | + |
| 462 | +#### **Technical Improvements** |
| 463 | +- Automated testing framework expansion |
| 464 | +- Performance optimization for large selections |
| 465 | +- Modern framework migration considerations |
| 466 | +- Enhanced retry mechanisms for critical operations |
| 467 | +- Storage integrity verification systems |
| 468 | + |
| 469 | +#### **Business Expansion** |
| 470 | +- Subscription model exploration |
| 471 | +- Partner integration opportunities |
| 472 | +- International market expansion |
| 473 | +- Advanced conversion optimization |
| 474 | + |
| 475 | +### Success Metrics |
| 476 | + |
| 477 | +#### **Primary KPIs** |
| 478 | +- License conversion rate from free to premium users |
| 479 | +- User retention and engagement metrics |
| 480 | +- Revenue per user and lifetime value |
| 481 | +- Customer satisfaction and support efficiency |
| 482 | + |
| 483 | +#### **Technical KPIs** |
| 484 | +- System uptime and reliability metrics |
| 485 | +- License activation success rate |
| 486 | +- Storage operation performance and error rates |
| 487 | +- User experience quality indicators |
306 | 488 |
|
307 | 489 | --- |
308 | 490 |
|
|
0 commit comments