@maccesar/titools 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/AGENTS-TEMPLATE.md +173 -0
  2. package/README.md +867 -0
  3. package/agents/ti-researcher.md +108 -0
  4. package/bin/titools.js +53 -0
  5. package/lib/commands/agents.js +126 -0
  6. package/lib/commands/install.js +188 -0
  7. package/lib/commands/uninstall.js +215 -0
  8. package/lib/commands/update.js +159 -0
  9. package/lib/config.js +119 -0
  10. package/lib/downloader.js +153 -0
  11. package/lib/installer.js +253 -0
  12. package/lib/platform.js +108 -0
  13. package/lib/symlink.js +142 -0
  14. package/lib/utils.js +270 -0
  15. package/package.json +67 -0
  16. package/skills/alloy-expert/SKILL.md +247 -0
  17. package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
  18. package/skills/alloy-expert/references/alloy-structure.md +381 -0
  19. package/skills/alloy-expert/references/anti-patterns.md +133 -0
  20. package/skills/alloy-expert/references/code-conventions.md +469 -0
  21. package/skills/alloy-expert/references/contracts.md +280 -0
  22. package/skills/alloy-expert/references/controller-patterns.md +520 -0
  23. package/skills/alloy-expert/references/error-handling.md +484 -0
  24. package/skills/alloy-expert/references/examples.md +735 -0
  25. package/skills/alloy-expert/references/migration-patterns.md +298 -0
  26. package/skills/alloy-expert/references/patterns.md +448 -0
  27. package/skills/alloy-expert/references/performance-patterns.md +855 -0
  28. package/skills/alloy-expert/references/security-patterns.md +847 -0
  29. package/skills/alloy-expert/references/state-management.md +779 -0
  30. package/skills/alloy-expert/references/testing.md +872 -0
  31. package/skills/alloy-guides/SKILL.md +214 -0
  32. package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
  33. package/skills/alloy-guides/references/CONCEPTS.md +191 -0
  34. package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
  35. package/skills/alloy-guides/references/MODELS.md +1028 -0
  36. package/skills/alloy-guides/references/PURGETSS.md +56 -0
  37. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
  38. package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
  39. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
  40. package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
  41. package/skills/alloy-guides/references/WIDGETS.md +176 -0
  42. package/skills/alloy-howtos/SKILL.md +203 -0
  43. package/skills/alloy-howtos/references/best_practices.md +138 -0
  44. package/skills/alloy-howtos/references/cli_reference.md +253 -0
  45. package/skills/alloy-howtos/references/config_files.md +87 -0
  46. package/skills/alloy-howtos/references/custom_tags.md +147 -0
  47. package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
  48. package/skills/alloy-howtos/references/samples.md +167 -0
  49. package/skills/purgetss/SKILL.md +442 -0
  50. package/skills/purgetss/assets/purgetss.config.cjs +17 -0
  51. package/skills/purgetss/references/EXAMPLES.md +247 -0
  52. package/skills/purgetss/references/animation-system.md +1294 -0
  53. package/skills/purgetss/references/apply-directive.md +375 -0
  54. package/skills/purgetss/references/arbitrary-values.md +612 -0
  55. package/skills/purgetss/references/class-index.md +1350 -0
  56. package/skills/purgetss/references/cli-commands.md +948 -0
  57. package/skills/purgetss/references/configurable-properties.md +654 -0
  58. package/skills/purgetss/references/custom-rules.md +161 -0
  59. package/skills/purgetss/references/customization-deep-dive.md +722 -0
  60. package/skills/purgetss/references/dynamic-component-creation.md +489 -0
  61. package/skills/purgetss/references/grid-layout.md +455 -0
  62. package/skills/purgetss/references/icon-fonts.md +609 -0
  63. package/skills/purgetss/references/installation-setup.md +366 -0
  64. package/skills/purgetss/references/opacity-modifier.md +291 -0
  65. package/skills/purgetss/references/platform-modifiers.md +479 -0
  66. package/skills/purgetss/references/smart-mappings.md +42 -0
  67. package/skills/purgetss/references/titanium-resets.md +359 -0
  68. package/skills/purgetss/references/ui-ux-design.md +1526 -0
  69. package/skills/ti-guides/SKILL.md +94 -0
  70. package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
  71. package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
  72. package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
  73. package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
  74. package/skills/ti-guides/references/android-manifest.md +97 -0
  75. package/skills/ti-guides/references/app-distribution.md +258 -0
  76. package/skills/ti-guides/references/application-frameworks.md +377 -0
  77. package/skills/ti-guides/references/cli-reference.md +402 -0
  78. package/skills/ti-guides/references/coding-best-practices.md +102 -0
  79. package/skills/ti-guides/references/commonjs-advanced.md +134 -0
  80. package/skills/ti-guides/references/hello-world.md +100 -0
  81. package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
  82. package/skills/ti-guides/references/javascript-primer.md +411 -0
  83. package/skills/ti-guides/references/reserved-words.md +36 -0
  84. package/skills/ti-guides/references/resources.md +183 -0
  85. package/skills/ti-guides/references/style-and-conventions.md +48 -0
  86. package/skills/ti-guides/references/tiapp-config.md +609 -0
  87. package/skills/ti-howtos/SKILL.md +174 -0
  88. package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
  89. package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
  90. package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
  91. package/skills/ti-howtos/references/cross-platform-development.md +348 -0
  92. package/skills/ti-howtos/references/debugging-profiling.md +543 -0
  93. package/skills/ti-howtos/references/extending-titanium.md +723 -0
  94. package/skills/ti-howtos/references/google-maps-v2.md +169 -0
  95. package/skills/ti-howtos/references/ios-map-kit.md +143 -0
  96. package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
  97. package/skills/ti-howtos/references/local-data-sources.md +301 -0
  98. package/skills/ti-howtos/references/location-and-maps.md +252 -0
  99. package/skills/ti-howtos/references/media-apis.md +210 -0
  100. package/skills/ti-howtos/references/notification-services.md +599 -0
  101. package/skills/ti-howtos/references/remote-data-sources.md +349 -0
  102. package/skills/ti-howtos/references/tutorials.md +502 -0
  103. package/skills/ti-howtos/references/using-modules.md +237 -0
  104. package/skills/ti-howtos/references/web-content-integration.md +307 -0
  105. package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
  106. package/skills/ti-ui/SKILL.md +179 -0
  107. package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
  108. package/skills/ti-ui/references/animation-and-matrices.md +599 -0
  109. package/skills/ti-ui/references/application-structures.md +655 -0
  110. package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
  111. package/skills/ti-ui/references/event-handling.md +393 -0
  112. package/skills/ti-ui/references/gestures.md +473 -0
  113. package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
  114. package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
  115. package/skills/ti-ui/references/listviews-and-performance.md +619 -0
  116. package/skills/ti-ui/references/orientation.md +362 -0
  117. package/skills/ti-ui/references/platform-ui-android.md +635 -0
  118. package/skills/ti-ui/references/platform-ui-ios.md +469 -0
  119. package/skills/ti-ui/references/scrolling-views.md +252 -0
  120. package/skills/ti-ui/references/tableviews.md +568 -0
@@ -0,0 +1,855 @@
1
+ # Performance Optimization Guide
2
+
3
+ ## Critical Rules
4
+
5
+ 1. **NEVER use `Ti.UI.SIZE` in ListView items** - Causes layout recalculation on every scroll
6
+ 2. **ALWAYS use fixed heights** - Pre-calculated heights enable fast scrolling
7
+ 3. **USE templates** - Reuse view instances instead of creating new ones
8
+ 4. **CACHE Ti.Platform properties** - Avoid repeated bridge crossings
9
+ 5. **USE applyProperties()** - Batch UI updates in a single call
10
+
11
+ ## ListView Performance
12
+
13
+ ### Optimized ListView Template
14
+
15
+ ```xml
16
+ <!-- views/user/list.xml -->
17
+ <Alloy>
18
+ <ListView class="wh-screen">
19
+ <Templates>
20
+ <!-- FIXED HEIGHT is critical for performance -->
21
+ <ItemTemplate name="userTemplate" height="64">
22
+ <View class="horizontal h-16 w-screen">
23
+ <ImageView bindId="avatar" class="rounded-full-12 ml-4" />
24
+ <View class="vertical ml-3">
25
+ <Label bindId="name" class="text-base font-bold" />
26
+ <Label bindId="email" class="text-sm text-gray-500" />
27
+ </View>
28
+ </View>
29
+ </ItemTemplate>
30
+ </Templates>
31
+
32
+ <ListSection id="section" dataCollection="users">
33
+ <ListItem template="userTemplate" avatar:image="{avatar}" name:text="{name}" email:text="{email}" />
34
+ </ListSection>
35
+ </ListView>
36
+ </Alloy>
37
+ ```
38
+
39
+ **PurgeTSS Layout Rules:**
40
+ - Use `horizontal`/`vertical` for layout (NOT flexbox)
41
+ - Use `m-*` on children for spacing (NOT `p-*` on parent)
42
+ - Use `wh-screen` for full width + height
43
+
44
+ ### Efficient Data Binding
45
+
46
+ ```javascript
47
+ // controllers/feed/list.js
48
+ function renderItems(items) {
49
+ // Pre-format data to avoid calculation in render
50
+ const listItems = items.map(item => ({
51
+ template: 'feedTemplate',
52
+ properties: {
53
+ itemId: item.id,
54
+ searchableText: `${item.title} ${item.description}`
55
+ },
56
+ title: { text: item.title },
57
+ description: { text: item.description },
58
+ timestamp: { text: formatTimestamp(item.created_at) }
59
+ }))
60
+
61
+ // Single update
62
+ $.section.items = listItems
63
+ }
64
+
65
+ // Timestamp formatter (cache results)
66
+ const timestampCache = new Map()
67
+
68
+ function formatTimestamp(timestamp) {
69
+ if (timestampCache.has(timestamp)) {
70
+ return timestampCache.get(timestamp)
71
+ }
72
+
73
+ const formatted = new Date(timestamp).toLocaleString()
74
+ timestampCache.set(timestamp, formatted)
75
+
76
+ return formatted
77
+ }
78
+ ```
79
+
80
+ ### Image Loading & Caching
81
+
82
+ ```javascript
83
+ // lib/services/imageCache.js
84
+ exports.ImageCache = {
85
+ _cache: new Map(),
86
+ _loading: new Map(),
87
+
88
+ // Get image at appropriate size for list item
89
+ getListThumbnail(url) {
90
+ const cacheKey = `thumb_${url}`
91
+
92
+ // Return cached if available
93
+ if (this._cache.has(cacheKey)) {
94
+ return this._cache.get(cacheKey)
95
+ }
96
+
97
+ // Check if already loading
98
+ if (this._loading.has(cacheKey)) {
99
+ return this._loading.get(cacheKey)
100
+ }
101
+
102
+ // Load and resize
103
+ const promise = this._loadAndResize(url, { width: 80, height: 80 })
104
+ .then(resized => {
105
+ this._cache.set(cacheKey, resized)
106
+ this._loading.delete(cacheKey)
107
+ return resized
108
+ })
109
+
110
+ this._loading.set(cacheKey, promise)
111
+
112
+ return promise
113
+ },
114
+
115
+ async _loadAndResize(url, size) {
116
+ return new Promise((resolve, reject) => {
117
+ const imageView = Ti.UI.createImageView({
118
+ image: url,
119
+ width: size.width,
120
+ height: size.height,
121
+ preventsDefaultAnimation: true
122
+ })
123
+
124
+ imageView.addEventListener('load', () => {
125
+ const blob = imageView.toImage()
126
+ const resized = blob.imageAsResized(size.width, size.height)
127
+ imageView.image = null
128
+ resolve(resized)
129
+ })
130
+
131
+ imageView.addEventListener('error', (e) => {
132
+ reject(e)
133
+ })
134
+ })
135
+ },
136
+
137
+ clear() {
138
+ this._cache.clear()
139
+ this._loading.clear()
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Bridge Optimization
145
+
146
+ ### Minimize Bridge Crossings
147
+
148
+ Every JavaScript → Native call crosses a bridge. Minimize these:
149
+
150
+ ```javascript
151
+ // BAD: Multiple bridge crossings
152
+ const width = Ti.Platform.displayCaps.platformWidth
153
+ const height = Ti.Platform.displayCaps.platformHeight
154
+ const dpi = Ti.Platform.displayCaps.dpi
155
+
156
+ // GOOD: Cache properties locally
157
+ const displayCaps = Ti.Platform.displayCaps
158
+ const { platformWidth, platformHeight, dpi } = displayCaps
159
+ ```
160
+
161
+ ### Batch UI Updates
162
+
163
+ ```javascript
164
+ // BAD: Multiple bridge crossings
165
+ $.nameLabel.text = user.name
166
+ $.nameLabel.color = '#000'
167
+ $.nameLabel.font = { fontSize: 16 }
168
+ $.nameLabel.left = 10
169
+
170
+ // GOOD: Single applyProperties call
171
+ $.nameLabel.applyProperties({
172
+ text: user.name,
173
+ color: '#000',
174
+ font: { fontSize: 16 },
175
+ left: 10
176
+ })
177
+ ```
178
+
179
+ ### Use PurgeTSS Classes
180
+
181
+ ```xml
182
+ <!-- BAD: Inline styling = more bridge crossings -->
183
+ <Label text="Hello" width="200" height="40" color="#000" font="{fontSize:16}" />
184
+
185
+ <!-- GOOD: Single class application -->
186
+ <Label text="Hello" class="h-10 w-1/2 text-base text-black" />
187
+ ```
188
+
189
+ ## Memory Management
190
+
191
+ ### Controller Cleanup Pattern
192
+
193
+ ```javascript
194
+ // controllers/detail.js
195
+ // Store references for cleanup
196
+ const _listeners = []
197
+ const _intervals = []
198
+ const _timers = []
199
+
200
+ function init() {
201
+ // Store listener reference
202
+ const updateHandler = onUpdate.bind(this)
203
+ Ti.App.addEventListener('app:update', updateHandler)
204
+ _listeners.push({ type: 'Ti.App', event: 'app:update', handler: updateHandler })
205
+
206
+ // Store interval reference
207
+ const intervalId = setInterval(pollData, 30000)
208
+ _intervals.push(intervalId)
209
+
210
+ // Store timer reference
211
+ const timerId = setTimeout(showTimeout, 5000)
212
+ _timers.push(timerId)
213
+ }
214
+
215
+ function cleanup() {
216
+ // Remove all listeners
217
+ _listeners.forEach(({ type, event, handler }) => {
218
+ if (type === 'Ti.App') {
219
+ Ti.App.removeEventListener(event, handler)
220
+ } else {
221
+ $.getView().removeEventListener(event, handler)
222
+ }
223
+ })
224
+
225
+ // Clear all intervals
226
+ _intervals.forEach(clearInterval)
227
+
228
+ // Clear all timers
229
+ _timers.forEach(clearTimeout)
230
+
231
+ // Null heavy objects
232
+ this._largeData = null
233
+ this._cachedImages = null
234
+
235
+ // Destroy Alloy bindings
236
+ $.destroy()
237
+
238
+ // Log cleanup
239
+ console.log('Detail controller cleaned up')
240
+ }
241
+
242
+ $.cleanup = cleanup
243
+ ```
244
+
245
+ ### Image Memory Management
246
+
247
+ ```javascript
248
+ // lib/services/imageManager.js
249
+ exports.ImageManager = {
250
+ // Resize images to avoid loading full-resolution into memory
251
+ resizeForDisplay(imageUrl, maxWidth, maxHeight) {
252
+ const imageView = Ti.UI.createImageView({
253
+ image: imageUrl,
254
+ width: Ti.UI.SIZE,
255
+ height: Ti.UI.SIZE
256
+ })
257
+
258
+ // Get actual size
259
+ const blob = imageView.toImage()
260
+
261
+ // Calculate aspect ratio
262
+ const aspect = blob.width / blob.height
263
+ let newWidth = maxWidth
264
+ let newHeight = maxWidth / aspect
265
+
266
+ if (newHeight > maxHeight) {
267
+ newHeight = maxHeight
268
+ newWidth = maxHeight * aspect
269
+ }
270
+
271
+ // Resize to reduce memory footprint
272
+ return blob.imageAsResized(newWidth, newHeight)
273
+ },
274
+
275
+ // Release image memory when done
276
+ release(imageView) {
277
+ if (!imageView) return
278
+
279
+ // Release image memory
280
+ if (imageView.image && imageView.image.release) {
281
+ imageView.image.release()
282
+ }
283
+
284
+ imageView.image = null
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## Lazy Loading Pattern
290
+
291
+ ```javascript
292
+ // controllers/feed/list.js
293
+ const PAGE_SIZE = 20
294
+ let currentPage = 1
295
+ let isLoading = false
296
+ let hasMore = true
297
+
298
+ function init() {
299
+ loadPage(1)
300
+
301
+ // Detect near end of list
302
+ $.listView.addEventListener('marker', onMarker)
303
+ }
304
+
305
+ function onMarker(e) {
306
+ if (!hasMore || isLoading) return
307
+
308
+ const totalItems = $.section.items?.length || 0
309
+
310
+ // Load more when 5 items from end
311
+ if (e.itemIndex >= totalItems - 5) {
312
+ loadNextPage()
313
+ }
314
+ }
315
+
316
+ async function loadNextPage() {
317
+ if (isLoading || !hasMore) return
318
+
319
+ isLoading = true
320
+
321
+ try {
322
+ const items = await api.getFeedPage(++currentPage, PAGE_SIZE)
323
+
324
+ if (items.length < PAGE_SIZE) {
325
+ hasMore = false
326
+ }
327
+
328
+ $.section.appendItems(items)
329
+
330
+ } catch (error) {
331
+ console.error('Failed to load more items', error)
332
+ currentPage-- // Retry same page
333
+ } finally {
334
+ isLoading = false
335
+ }
336
+ }
337
+
338
+ function cleanup() {
339
+ $.listView.removeEventListener('marker', onMarker)
340
+ $.destroy()
341
+ }
342
+
343
+ $.cleanup = cleanup
344
+ ```
345
+
346
+ ## Database Performance
347
+
348
+ ```javascript
349
+ // lib/services/database.js
350
+ exports.DB = {
351
+ // Use transactions for multiple writes
352
+ batchInsert(items) {
353
+ const db = Ti.Database.open('mydb')
354
+
355
+ try {
356
+ db.begin_transaction()
357
+
358
+ items.forEach(item => {
359
+ db.execute(
360
+ 'INSERT INTO items (name, value, created_at) VALUES (?, ?, ?)',
361
+ item.name,
362
+ item.value,
363
+ Date.now()
364
+ )
365
+ })
366
+
367
+ db.commit()
368
+
369
+ console.log(`Inserted ${items.length} items`)
370
+
371
+ } catch (e) {
372
+ db.rollback()
373
+ console.error('Batch insert failed', e)
374
+ throw e
375
+
376
+ } finally {
377
+ db.close()
378
+ }
379
+ },
380
+
381
+ // Use prepared statements for repeated queries
382
+ findUsersByName(namePattern) {
383
+ const db = Ti.Database.open('mydb')
384
+
385
+ try {
386
+ const rows = db.execute(
387
+ 'SELECT id, name, email FROM users WHERE name LIKE ?',
388
+ `%${namePattern}%`
389
+ )
390
+
391
+ const users = []
392
+
393
+ while (rows.isValidRow()) {
394
+ users.push({
395
+ id: rows.fieldByName('id'),
396
+ name: rows.fieldByName('name'),
397
+ email: rows.fieldByName('email')
398
+ })
399
+ rows.next()
400
+ }
401
+
402
+ rows.close()
403
+
404
+ return users
405
+
406
+ } finally {
407
+ db.close()
408
+ }
409
+ },
410
+
411
+ // Create indexes for frequently queried columns
412
+ createIndexes() {
413
+ const db = Ti.Database.open('mydb')
414
+
415
+ db.execute('CREATE INDEX IF NOT EXISTS idx_items_name ON items(name)')
416
+ db.execute('CREATE INDEX IF NOT EXISTS idx_items_date ON items(created_at)')
417
+
418
+ db.close()
419
+ }
420
+ }
421
+ ```
422
+
423
+ ## Performance Monitoring
424
+
425
+ ```javascript
426
+ // lib/services/perfMonitor.js
427
+ exports.Perf = {
428
+ _metrics: {
429
+ renders: [],
430
+ bridgeCrossings: 0
431
+ },
432
+
433
+ start(label) {
434
+ const start = Date.now()
435
+
436
+ return {
437
+ label,
438
+ start,
439
+
440
+ end() {
441
+ const duration = Date.now() - this.start
442
+ Perf._metrics.renders.push({ label: this.label, duration })
443
+
444
+ console.log(`[PERF] ${this.label}: ${duration}ms`)
445
+
446
+ if (duration > 100) {
447
+ console.warn(`[PERF] SLOW: ${this.label} took ${duration}ms`)
448
+ }
449
+ }
450
+ }
451
+ },
452
+
453
+ countBridgeCall() {
454
+ this._metrics.bridgeCrossings++
455
+ },
456
+
457
+ logMemoryUsage() {
458
+ const available = Ti.Platform.availableMemory
459
+
460
+ console.log(`[MEMORY] Available: ${available}MB`)
461
+ },
462
+
463
+ getReport() {
464
+ const avgRender = this._metrics.renders.reduce((sum, m) =>
465
+ sum + m.duration, 0
466
+ ) / (this._metrics.renders.length || 1)
467
+
468
+ return {
469
+ averageRenderTime: Math.round(avgRender),
470
+ totalBridgeCalls: this._metrics.bridgeCrossings,
471
+ slowOperations: this._metrics.renders.filter(m => m.duration > 100)
472
+ }
473
+ }
474
+ }
475
+
476
+ // Usage example
477
+ const measure = Perf.start('user_list_render')
478
+ renderUsers(users)
479
+ measure.end()
480
+ ```
481
+
482
+ ## Performance Checklist
483
+
484
+ | Area | Check |
485
+ | ------------ | ----------------------------------------- |
486
+ | **ListView** | Fixed heights on all templates |
487
+ | **ListView** | Using templates, not dynamic views |
488
+ | **ListView** | Image pre-sizing and caching |
489
+ | **Bridge** | Cached Ti.Platform properties |
490
+ | **Bridge** | Using applyProperties for updates |
491
+ | **Bridge** | PurgeTSS classes instead of inline styles |
492
+ | **Memory** | All global listeners cleaned up |
493
+ | **Memory** | Heavy objects nulled in cleanup |
494
+ | **Memory** | Images resized appropriately |
495
+ | **Database** | Using transactions for batch ops |
496
+ | **Database** | Indexes on frequently queried columns |
497
+ | **Database** | ResultSets and DB handles closed |
498
+
499
+ ## ScrollView Performance
500
+
501
+ ### Optimizing Large ScrollViews
502
+
503
+ ```xml
504
+ <!-- Avoid: Creating many views at once -->
505
+ <ScrollView class="wh-screen vertical">
506
+ <!-- DON'T: 100+ views created immediately -->
507
+ </ScrollView>
508
+
509
+ <!-- Better: Use ListView for list-like content -->
510
+ <ListView class="wh-screen">
511
+ <!-- Views created on-demand as user scrolls -->
512
+ </ListView>
513
+ ```
514
+
515
+ ### When You Must Use ScrollView
516
+
517
+ ```javascript
518
+ // Lazy load content sections
519
+ const sections = [
520
+ { id: 'header', height: 200 },
521
+ { id: 'featured', height: 300 },
522
+ { id: 'products', height: 400 },
523
+ { id: 'reviews', height: 500 }
524
+ ]
525
+
526
+ let loadedSections = new Set()
527
+
528
+ function init() {
529
+ // Load only visible sections initially
530
+ loadSection('header')
531
+
532
+ $.scrollView.addEventListener('scroll', onScroll)
533
+ }
534
+
535
+ function onScroll(e) {
536
+ const scrollY = e.y
537
+ const viewportHeight = $.scrollView.rect.height
538
+
539
+ sections.forEach(section => {
540
+ if (loadedSections.has(section.id)) return
541
+
542
+ // Check if section is about to be visible
543
+ const sectionTop = getSectionTop(section.id)
544
+
545
+ if (sectionTop < scrollY + viewportHeight + 100) {
546
+ loadSection(section.id)
547
+ }
548
+ })
549
+ }
550
+
551
+ function loadSection(sectionId) {
552
+ if (loadedSections.has(sectionId)) return
553
+
554
+ loadedSections.add(sectionId)
555
+
556
+ // Load content for this section
557
+ const container = $[sectionId + 'Container']
558
+ const content = createSectionContent(sectionId)
559
+ container.add(content)
560
+ }
561
+ ```
562
+
563
+ ### ScrollView Memory Management
564
+
565
+ ```javascript
566
+ // Release images when scrolled far away
567
+ function onScroll(e) {
568
+ const scrollY = e.y
569
+ const viewportHeight = $.scrollView.rect.height
570
+
571
+ // Release images more than 2 screens away
572
+ const releaseThreshold = viewportHeight * 2
573
+
574
+ imageViews.forEach((img, index) => {
575
+ const imgTop = img.rect.y
576
+ const distance = Math.abs(imgTop - scrollY)
577
+
578
+ if (distance > releaseThreshold && img.image) {
579
+ // Store URL for later reload
580
+ img._originalUrl = img.image
581
+ img.image = null
582
+ } else if (distance < viewportHeight && img._originalUrl) {
583
+ // Reload when close to viewport
584
+ img.image = img._originalUrl
585
+ delete img._originalUrl
586
+ }
587
+ })
588
+ }
589
+ ```
590
+
591
+ ## Animation Performance
592
+
593
+ ### 60fps Animation Rules
594
+
595
+ ```javascript
596
+ // Rule 1: Use native animations (not JavaScript intervals)
597
+ // BAD: JavaScript-driven animation
598
+ let x = 0
599
+ setInterval(() => {
600
+ x += 1
601
+ $.view.left = x // 60 bridge crossings per second!
602
+ }, 16)
603
+
604
+ // GOOD: Native animation
605
+ const animation = Ti.UI.createAnimation({
606
+ left: 100,
607
+ duration: 500,
608
+ curve: Ti.UI.ANIMATION_CURVE_EASE_OUT
609
+ })
610
+ $.view.animate(animation)
611
+ ```
612
+
613
+ ### PurgeTSS Animation Component (Recommended)
614
+
615
+ ```xml
616
+ <!-- Define animations with state modifiers -->
617
+ <Animation id="fadeIn" module="purgetss.ui" class="close:opacity-0 duration-300 open:opacity-100" />
618
+ <Animation id="fadeOut" module="purgetss.ui" class="close:opacity-100 duration-300 open:opacity-0" />
619
+ <Animation id="slideInRight" module="purgetss.ui" class="close:translate-x-full duration-300 open:translate-x-0" />
620
+ <Animation id="scalePress" module="purgetss.ui" class="close:scale-100 duration-150 open:scale-95" />
621
+ ```
622
+
623
+ ```javascript
624
+ // Fade in/out
625
+ $.fadeIn.open($.card)
626
+ $.fadeOut.close($.card)
627
+
628
+ // Slide animations
629
+ $.slideInRight.open($.panel)
630
+ $.slideInRight.close($.panel)
631
+
632
+ // Scale animations (press effect)
633
+ $.scalePress.open($.button) // Scale down
634
+ $.scalePress.close($.button) // Scale back up
635
+
636
+ // Chained animations with callback
637
+ $.fadeIn.open($.modal, () => {
638
+ // Animation complete, trigger next animation
639
+ $.slideInRight.open($.content)
640
+ })
641
+ ```
642
+
643
+ ### Hardware-Accelerated Properties
644
+
645
+ ```javascript
646
+ // These properties are GPU-accelerated (fast):
647
+ // - opacity
648
+ // - transform (translate, scale, rotate)
649
+
650
+ // These trigger layout recalculation (slow):
651
+ // - width, height
652
+ // - top, left, right, bottom
653
+ // - visible
654
+
655
+ // GOOD: Animate opacity for fade effects
656
+ const fadeOut = Ti.UI.createAnimation({
657
+ opacity: 0,
658
+ duration: 200
659
+ })
660
+
661
+ // GOOD: Use transform for movement
662
+ const slideRight = Ti.UI.createAnimation({
663
+ duration: 300,
664
+ transform: Ti.UI.createMatrix2D().translate(100, 0),
665
+ })
666
+
667
+ // AVOID: Animating layout properties
668
+ const badAnimation = Ti.UI.createAnimation({
669
+ left: 100, // Triggers layout recalc
670
+ width: 200, // Triggers layout recalc
671
+ duration: 300
672
+ })
673
+ ```
674
+
675
+ ### Animation Cleanup
676
+
677
+ ```javascript
678
+ // Always remove animation listeners
679
+ let currentAnimation = null
680
+
681
+ function animateIn() {
682
+ currentAnimation = Ti.UI.createAnimation({
683
+ opacity: 1,
684
+ duration: 300
685
+ })
686
+
687
+ const onComplete = () => {
688
+ currentAnimation.removeEventListener('complete', onComplete)
689
+ currentAnimation = null
690
+ }
691
+
692
+ currentAnimation.addEventListener('complete', onComplete)
693
+ $.view.animate(currentAnimation)
694
+ }
695
+
696
+ function cleanup() {
697
+ // Cancel any running animation
698
+ if (currentAnimation) {
699
+ $.view.animate({ duration: 0 }) // Cancel
700
+ currentAnimation = null
701
+ }
702
+ $.destroy()
703
+ }
704
+
705
+ $.cleanup = cleanup
706
+ ```
707
+
708
+ ## Debouncing and Throttling
709
+
710
+ ### Debounce Pattern
711
+
712
+ Use when you want to wait for the user to stop an action before processing.
713
+
714
+ ```javascript
715
+ // lib/helpers/timing.js
716
+ exports.debounce = function debounce(fn, delay = 300) {
717
+ let timeoutId = null
718
+
719
+ const debounced = function(...args) {
720
+ clearTimeout(timeoutId)
721
+ timeoutId = setTimeout(() => {
722
+ fn.apply(this, args)
723
+ }, delay)
724
+ }
725
+
726
+ debounced.cancel = () => {
727
+ clearTimeout(timeoutId)
728
+ timeoutId = null
729
+ }
730
+
731
+ return debounced
732
+ }
733
+ ```
734
+
735
+ ```javascript
736
+ // Usage: Search input
737
+ const { debounce } = require('lib/helpers/timing')
738
+
739
+ const debouncedSearch = debounce(async (query) => {
740
+ const results = await searchService.search(query)
741
+ renderResults(results)
742
+ }, 300)
743
+
744
+ function onSearchChange(e) {
745
+ debouncedSearch(e.value)
746
+ }
747
+
748
+ function cleanup() {
749
+ debouncedSearch.cancel()
750
+ $.destroy()
751
+ }
752
+ ```
753
+
754
+ ### Throttle Pattern
755
+
756
+ Use when you want to limit how often a function can run.
757
+
758
+ ```javascript
759
+ // lib/helpers/timing.js
760
+ exports.throttle = function throttle(fn, limit = 100) {
761
+ let lastRun = 0
762
+ let timeoutId = null
763
+
764
+ const throttled = function(...args) {
765
+ const now = Date.now()
766
+ const remaining = limit - (now - lastRun)
767
+
768
+ if (remaining <= 0) {
769
+ lastRun = now
770
+ fn.apply(this, args)
771
+ } else if (!timeoutId) {
772
+ timeoutId = setTimeout(() => {
773
+ lastRun = Date.now()
774
+ timeoutId = null
775
+ fn.apply(this, args)
776
+ }, remaining)
777
+ }
778
+ }
779
+
780
+ throttled.cancel = () => {
781
+ clearTimeout(timeoutId)
782
+ timeoutId = null
783
+ }
784
+
785
+ return throttled
786
+ }
787
+ ```
788
+
789
+ ```javascript
790
+ // Usage: Scroll handler
791
+ const { throttle } = require('lib/helpers/timing')
792
+
793
+ const throttledScroll = throttle((scrollY) => {
794
+ updateHeaderOpacity(scrollY)
795
+ checkLazyLoadImages(scrollY)
796
+ }, 50) // Max 20 calls per second
797
+
798
+ function onScroll(e) {
799
+ throttledScroll(e.y)
800
+ }
801
+
802
+ function cleanup() {
803
+ throttledScroll.cancel()
804
+ $.scrollView.removeEventListener('scroll', onScroll)
805
+ $.destroy()
806
+ }
807
+ ```
808
+
809
+ ### Common Use Cases
810
+
811
+ | Pattern | Use Case | Delay |
812
+ | -------- | ---------------- | ------------ |
813
+ | Debounce | Search input | 300ms |
814
+ | Debounce | Auto-save | 1000ms |
815
+ | Debounce | Window resize | 150ms |
816
+ | Throttle | Scroll events | 50-100ms |
817
+ | Throttle | Mouse/touch move | 16ms (60fps) |
818
+ | Throttle | API polling | 5000ms+ |
819
+
820
+ ### Combined Pattern for Real-time + Final
821
+
822
+ ```javascript
823
+ // Show immediate feedback while typing, but only search when done
824
+ const { debounce, throttle } = require('lib/helpers/timing')
825
+
826
+ // Update UI immediately (throttled)
827
+ const updateSuggestions = throttle((query) => {
828
+ // Filter local cache for quick suggestions
829
+ const suggestions = localCache.filter(q => q.includes(query))
830
+ renderSuggestions(suggestions)
831
+ }, 100)
832
+
833
+ // Search API only when typing stops (debounced)
834
+ const searchApi = debounce(async (query) => {
835
+ const results = await api.search(query)
836
+ renderResults(results)
837
+ }, 500)
838
+
839
+ function onSearchChange(e) {
840
+ const query = e.value.trim()
841
+
842
+ if (query.length > 0) {
843
+ updateSuggestions(query) // Immediate feedback
844
+ searchApi(query) // API call when done typing
845
+ } else {
846
+ clearResults()
847
+ }
848
+ }
849
+
850
+ function cleanup() {
851
+ updateSuggestions.cancel()
852
+ searchApi.cancel()
853
+ $.destroy()
854
+ }
855
+ ```