@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,735 @@
1
+ # PurgeTSS Implementation Examples (Alloy + PurgeTSS)
2
+
3
+ ## API Client Service
4
+ Standard logic for network requests.
5
+
6
+ ```javascript
7
+ // lib/api/client.js
8
+ exports.get = function(endpoint, params = {}) {
9
+ return new Promise(function(resolve, reject) {
10
+ const client = Ti.Network.createHTTPClient({
11
+ onload: function() { resolve(JSON.parse(client.responseText)) },
12
+ onerror: function(e) { reject(e) },
13
+ timeout: 5000
14
+ })
15
+ client.open('GET', endpoint)
16
+ client.send()
17
+ })
18
+ }
19
+ ```
20
+
21
+ ## Native Module Wrapper Service
22
+ Encapsulate native modules (e.g., audio, maps, facebook) to decouple from controllers.
23
+
24
+ ```javascript
25
+ // lib/services/nativeService.js
26
+ const someModule = require('ti.someModule')
27
+
28
+ exports.performAction = function(data) {
29
+ someModule.doSomething({
30
+ data: data,
31
+ status: L('processing')
32
+ })
33
+ }
34
+ ```
35
+
36
+ ## i18n Helper
37
+ Logic for complex string transformations and plurals.
38
+
39
+ ```javascript
40
+ // lib/helpers/i18n.js
41
+ exports.getPluralMessages = function(count) {
42
+ const key = count === 1 ? 'one_message' : 'many_messages'
43
+ return L(key).replace('%d', count)
44
+ }
45
+ ```
46
+
47
+ ## Model with SQL Adapter
48
+ Definition for local SQLite persistence.
49
+
50
+ ```javascript
51
+ // models/User.js
52
+ exports.definition = {
53
+ config: {
54
+ columns: {
55
+ id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
56
+ name: 'TEXT',
57
+ email: 'TEXT'
58
+ },
59
+ adapter: {
60
+ type: 'sql',
61
+ collection_name: 'users'
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Fully Styled & Accessible View (PurgeTSS)
68
+ Applying PurgeTSS classes while maintaining accessibility.
69
+
70
+ ```xml
71
+ <!-- views/login.xml -->
72
+ <Alloy>
73
+ <Window class="vertical bg-gray-50">
74
+ <Animation id="myAnim" module="purgetss.ui" class="close:opacity-0 duration-500 open:opacity-100" />
75
+
76
+ <!-- Spacer to center content -->
77
+ <View class="h-auto" />
78
+
79
+ <Label class="text-primary fa-solid fa-lock mx-6 mb-10 text-4xl"
80
+ accessibilityLabel="L('login_icon_label')"
81
+ />
82
+
83
+ <TextField id="email"
84
+ class="border-(1) mx-6 h-12 w-screen rounded-lg border-gray-300 bg-white"
85
+ hintText="L('email_hint')"
86
+ accessibilityLabel="L('email_label')"
87
+ />
88
+
89
+ <Button id="submit"
90
+ class="bg-primary mx-6 mt-10 h-14 w-screen rounded-xl font-bold text-white"
91
+ title="L('login_button')"
92
+ accessibilityLabel="L('login_button')"
93
+ accessibilityHint="L('login_hint')"
94
+ onClick="doLogin"
95
+ />
96
+
97
+ <!-- Spacer to center content -->
98
+ <View class="h-auto" />
99
+ </Window>
100
+ </Alloy>
101
+ ```
102
+
103
+ **Notes:**
104
+ - Use `vertical` layout on Window (not `flex-col`)
105
+ - Use `mx-6` for horizontal padding (not `p-6` on parent)
106
+ - Use `w-screen` for full width (not `w-full`)
107
+ - Use `border-(1)` with parentheses for arbitrary border width
108
+
109
+ ## Cleanup Pattern in Controller
110
+ Critical for memory management and avoiding leaks.
111
+
112
+ ```javascript
113
+ // controllers/login.js
114
+ const onNotification = (e) => {
115
+ Ti.API.info('Notification received')
116
+ }
117
+
118
+ Ti.App.addEventListener('notification', onNotification)
119
+
120
+ function cleanup() {
121
+ // Always remove app-level listeners
122
+ Ti.App.removeEventListener('notification', onNotification)
123
+ // Destroy Alloy bindings
124
+ $.destroy()
125
+ }
126
+
127
+ // Expose for Navigation Service
128
+ $.cleanup = cleanup
129
+ ```
130
+
131
+ ## Animation Component Usage
132
+ Using the toolkit for UI transformations via the `<Animation>` component.
133
+
134
+ ```javascript
135
+ // Inside any controller
136
+ function shakeError(element) {
137
+ $.myAnim.play(element, 'animate-shake duration-200')
138
+ }
139
+ ```
140
+
141
+ ## Complete CRUD Example
142
+
143
+ ```javascript
144
+ // lib/services/productService.js
145
+ const { productApi } = require('lib/api/productApi')
146
+ const { appStore } = require('lib/services/stateStore')
147
+ const logger = require('lib/services/logger')
148
+
149
+ exports.productService = {
150
+ getAll: async function(filters = {}) {
151
+ logger.debug('ProductService', 'Fetching products', filters)
152
+
153
+ appStore.setState({ 'ui.isLoading': true })
154
+
155
+ try {
156
+ const products = await productApi.getAll(filters)
157
+ Alloy.Collections.products.reset(products)
158
+ return products
159
+ } finally {
160
+ appStore.setState({ 'ui.isLoading': false })
161
+ }
162
+ },
163
+
164
+ getById: async function(id) {
165
+ return productApi.getById(id)
166
+ },
167
+
168
+ create: async function(data) {
169
+ const product = await productApi.create(data)
170
+ Alloy.Collections.products.add(product)
171
+ logger.info('ProductService', 'Product created', { id: product.id })
172
+ return product
173
+ },
174
+
175
+ update: async function(id, data) {
176
+ const product = await productApi.update(id, data)
177
+
178
+ // Update in collection
179
+ const existing = Alloy.Collections.products.get(id)
180
+ if (existing) {
181
+ existing.set(product)
182
+ }
183
+
184
+ logger.info('ProductService', 'Product updated', { id })
185
+ return product
186
+ },
187
+
188
+ delete: async function(id) {
189
+ await productApi.delete(id)
190
+ Alloy.Collections.products.remove(id)
191
+ logger.info('ProductService', 'Product deleted', { id })
192
+ }
193
+ }
194
+ ```
195
+
196
+ ```xml
197
+ <!-- views/products/list.xml -->
198
+ <Alloy>
199
+ <Window class="bg-gray-50">
200
+ <ListView id="listView" class="wh-screen">
201
+ <RefreshControl id="refresh" onRefresh="onRefresh" />
202
+
203
+ <Templates>
204
+ <ItemTemplate name="product" height="80">
205
+ <View class="horizontal mb-2 h-20 w-screen bg-white">
206
+ <ImageView bindId="image" class="wh-16 ml-3 rounded-lg" />
207
+ <View class="vertical ml-3 w-auto">
208
+ <Label bindId="name" class="text-base font-semibold" />
209
+ <Label bindId="price" class="text-sm text-green-600" />
210
+ <Label bindId="stock" class="text-xs text-gray-400" />
211
+ </View>
212
+ </View>
213
+ </ItemTemplate>
214
+ </Templates>
215
+
216
+ <ListSection id="section" dataCollection="products">
217
+ <ListItem template="product"
218
+ image:image="{imageUrl}"
219
+ name:text="{name}"
220
+ price:text="${price}"
221
+ stock:text="{stock} in stock"
222
+ />
223
+ </ListSection>
224
+ </ListView>
225
+
226
+ <Button id="addBtn"
227
+ class="bg-primary rounded-full-14 absolute bottom-6 right-6 shadow-lg"
228
+ onClick="onAddProduct"
229
+ >
230
+ <Label class="fa-solid fa-plus text-xl text-white" />
231
+ </Button>
232
+ </Window>
233
+ </Alloy>
234
+ ```
235
+
236
+ ```javascript
237
+ // controllers/products/list.js
238
+ const { Navigation } = require('lib/services/navigation')
239
+ const { productService } = require('lib/services/productService')
240
+
241
+ function init() {
242
+ loadProducts()
243
+
244
+ $.listView.addEventListener('itemclick', onItemClick)
245
+ }
246
+
247
+ async function loadProducts() {
248
+ try {
249
+ await productService.getAll()
250
+ } catch (error) {
251
+ showError(error.message)
252
+ }
253
+ }
254
+
255
+ async function onRefresh() {
256
+ try {
257
+ await productService.getAll()
258
+ } finally {
259
+ $.refresh.endRefreshing()
260
+ }
261
+ }
262
+
263
+ function onItemClick(e) {
264
+ const productId = e.itemId
265
+ Navigation.open('products/detail', { productId })
266
+ }
267
+
268
+ function onAddProduct() {
269
+ const ctrl = Navigation.open('products/edit', { mode: 'create' })
270
+ ctrl.on('product:saved', loadProducts)
271
+ }
272
+
273
+ function cleanup() {
274
+ $.listView.removeEventListener('itemclick', onItemClick)
275
+ $.destroy()
276
+ }
277
+
278
+ $.cleanup = cleanup
279
+ ```
280
+
281
+ ## ListView with Search and Filter
282
+
283
+ ```xml
284
+ <!-- views/contacts/list.xml -->
285
+ <Alloy>
286
+ <Window class="bg-white">
287
+ <!-- Search and Filter Bar -->
288
+ <View class="horizontal h-14 w-screen bg-gray-100">
289
+ <SearchBar id="searchBar"
290
+ class="h-10 w-8/12"
291
+ hintText="L('search')"
292
+ showCancel="true"
293
+ />
294
+ <Button id="filterBtn"
295
+ class="h-10 w-4/12"
296
+ title="L('filter')"
297
+ onClick="showFilters"
298
+ />
299
+ </View>
300
+
301
+ <!-- Active Filters -->
302
+ <ScrollView id="filterTags" class="horizontal hidden h-10 w-screen">
303
+ <!-- Dynamically populated filter tags -->
304
+ </ScrollView>
305
+
306
+ <ListView id="listView" class="wh-screen">
307
+ <ListSection id="section" />
308
+ </ListView>
309
+
310
+ <!-- Empty State -->
311
+ <View id="emptyState" class="wh-screen vertical hidden">
312
+ <Label class="fa-solid fa-search mt-20 text-6xl text-gray-300" />
313
+ <Label class="mt-4 text-lg text-gray-500" text="L('no_results')" />
314
+ </View>
315
+ </Window>
316
+ </Alloy>
317
+ ```
318
+
319
+ ```javascript
320
+ // controllers/contacts/list.js
321
+ let allItems = []
322
+ let activeFilters = { category: null, status: null }
323
+ let searchQuery = ''
324
+ let debounceTimer = null
325
+
326
+ function init() {
327
+ loadData()
328
+
329
+ $.searchBar.addEventListener('cancel', clearSearch)
330
+ $.searchBar.addEventListener('change', onSearchChange)
331
+ }
332
+
333
+ function onSearchChange(e) {
334
+ clearTimeout(debounceTimer)
335
+ debounceTimer = setTimeout(() => {
336
+ searchQuery = e.value.toLowerCase().trim()
337
+ applyFilters()
338
+ }, 300)
339
+ }
340
+
341
+ function clearSearch() {
342
+ searchQuery = ''
343
+ applyFilters()
344
+ }
345
+
346
+ function showFilters() {
347
+ const dialog = Ti.UI.createOptionDialog({
348
+ title: L('filter_by_category'),
349
+ options: ['All', 'Work', 'Personal', 'Family', L('cancel')],
350
+ cancel: 4
351
+ })
352
+
353
+ dialog.addEventListener('click', (e) => {
354
+ if (e.index < 4) {
355
+ activeFilters.category = e.index === 0 ? null : ['work', 'personal', 'family'][e.index - 1]
356
+ updateFilterTags()
357
+ applyFilters()
358
+ }
359
+ })
360
+
361
+ dialog.show()
362
+ }
363
+
364
+ function applyFilters() {
365
+ let filtered = [...allItems]
366
+
367
+ // Apply search
368
+ if (searchQuery) {
369
+ filtered = filtered.filter(item =>
370
+ item.name.toLowerCase().includes(searchQuery) ||
371
+ item.email?.toLowerCase().includes(searchQuery)
372
+ )
373
+ }
374
+
375
+ // Apply category filter
376
+ if (activeFilters.category) {
377
+ filtered = filtered.filter(item => item.category === activeFilters.category)
378
+ }
379
+
380
+ renderItems(filtered)
381
+
382
+ // Show/hide empty state
383
+ $.emptyState.visible = filtered.length === 0
384
+ $.listView.visible = filtered.length > 0
385
+ }
386
+
387
+ function updateFilterTags() {
388
+ // Clear existing tags
389
+ $.filterTags.removeAllChildren()
390
+
391
+ const hasFilters = activeFilters.category || activeFilters.status
392
+ $.filterTags.visible = hasFilters
393
+
394
+ if (activeFilters.category) {
395
+ const tag = createFilterTag(activeFilters.category, () => {
396
+ activeFilters.category = null
397
+ updateFilterTags()
398
+ applyFilters()
399
+ })
400
+ $.filterTags.add(tag)
401
+ }
402
+ }
403
+
404
+ function createFilterTag(label, onRemove) {
405
+ const tag = Ti.UI.createView({ width: Ti.UI.SIZE, height: 32 })
406
+ tag.add(Ti.UI.createLabel({ text: label }))
407
+
408
+ const closeBtn = Ti.UI.createLabel({ text: '×', right: 4 })
409
+ closeBtn.addEventListener('click', onRemove)
410
+ tag.add(closeBtn)
411
+
412
+ return tag
413
+ }
414
+
415
+ function cleanup() {
416
+ clearTimeout(debounceTimer)
417
+ $.searchBar.removeEventListener('change', onSearchChange)
418
+ $.destroy()
419
+ }
420
+
421
+ $.cleanup = cleanup
422
+ ```
423
+
424
+ ## Form with Validation Example
425
+
426
+ ```xml
427
+ <!-- views/auth/register.xml -->
428
+ <Alloy>
429
+ <Window class="vertical bg-white">
430
+ <ScrollView class="wh-screen vertical" contentHeight="Ti.UI.SIZE">
431
+ <View class="vertical mt-8 h-auto w-screen">
432
+ <!-- Logo -->
433
+ <ImageView class="wh-24" image="/images/logo.png" />
434
+
435
+ <!-- Title -->
436
+ <Label class="mt-6 text-2xl font-bold" text="L('create_account')" />
437
+
438
+ <!-- Name Field -->
439
+ <View class="mx-4 mt-6 w-screen">
440
+ <Label class="text-sm text-gray-600" text="L('full_name')" />
441
+ <TextField id="nameField"
442
+ class="border-(1) return-key-type-next mt-1 h-12 w-screen rounded-lg border-gray-300 px-3"
443
+ autocorrect="false"
444
+ />
445
+ <Label id="nameError" class="mt-1 hidden text-xs text-red-500" />
446
+ </View>
447
+
448
+ <!-- Email Field -->
449
+ <View class="mx-4 mt-4 w-screen">
450
+ <Label class="text-sm text-gray-600" text="L('email')" />
451
+ <TextField id="emailField"
452
+ class="border-(1) keyboard-type-email return-key-type-next mt-1 h-12 w-screen rounded-lg border-gray-300 px-3"
453
+ autocapitalization="none"
454
+ />
455
+ <Label id="emailError" class="mt-1 hidden text-xs text-red-500" />
456
+ </View>
457
+
458
+ <!-- Password Field -->
459
+ <View class="mx-4 mt-4 w-screen">
460
+ <Label class="text-sm text-gray-600" text="L('password')" />
461
+ <TextField id="passwordField"
462
+ class="border-(1) return-key-type-next mt-1 h-12 w-screen rounded-lg border-gray-300 px-3"
463
+ passwordMask="true"
464
+ />
465
+ <Label id="passwordError" class="mt-1 hidden text-xs text-red-500" />
466
+ <Label class="mt-1 text-xs text-gray-400" text="L('password_hint')" />
467
+ </View>
468
+
469
+ <!-- Confirm Password -->
470
+ <View class="mx-4 mt-4 w-screen">
471
+ <Label class="text-sm text-gray-600" text="L('confirm_password')" />
472
+ <TextField id="confirmField"
473
+ class="border-(1) return-key-type-done mt-1 h-12 w-screen rounded-lg border-gray-300 px-3"
474
+ passwordMask="true"
475
+ />
476
+ <Label id="confirmError" class="mt-1 hidden text-xs text-red-500" />
477
+ </View>
478
+
479
+ <!-- Terms Checkbox -->
480
+ <View class="horizontal mx-4 mt-6 w-screen">
481
+ <Switch id="termsSwitch" class="w-12" />
482
+ <Label class="ml-2 w-auto text-sm text-gray-600" text="L('accept_terms')" />
483
+ </View>
484
+
485
+ <!-- Register Button -->
486
+ <Button id="registerBtn"
487
+ class="bg-primary mx-4 mt-6 h-14 w-screen rounded-xl font-bold text-white"
488
+ title="L('register')"
489
+ onClick="onRegister"
490
+ />
491
+
492
+ <!-- Login Link -->
493
+ <View class="horizontal mt-4">
494
+ <Label class="text-sm text-gray-600" text="L('have_account')" />
495
+ <Label class="text-primary ml-1 text-sm" text="L('login')" onClick="goToLogin" />
496
+ </View>
497
+ </View>
498
+ </ScrollView>
499
+ </Window>
500
+ </Alloy>
501
+ ```
502
+
503
+ ```javascript
504
+ // controllers/auth/register.js
505
+ const { AuthService } = require('lib/services/authService')
506
+ const { Navigation } = require('lib/services/navigation')
507
+
508
+ const validators = {
509
+ name: (value) => {
510
+ if (!value?.trim()) return L('error_name_required')
511
+ if (value.trim().length < 2) return L('error_name_short')
512
+ return null
513
+ },
514
+
515
+ email: (value) => {
516
+ if (!value?.trim()) return L('error_email_required')
517
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
518
+ if (!regex.test(value)) return L('error_email_invalid')
519
+ return null
520
+ },
521
+
522
+ password: (value) => {
523
+ if (!value) return L('error_password_required')
524
+ if (value.length < 8) return L('error_password_short')
525
+ if (!/[A-Z]/.test(value)) return L('error_password_uppercase')
526
+ if (!/[0-9]/.test(value)) return L('error_password_number')
527
+ return null
528
+ },
529
+
530
+ confirm: (value, password) => {
531
+ if (!value) return L('error_confirm_required')
532
+ if (value !== password) return L('error_passwords_mismatch')
533
+ return null
534
+ }
535
+ }
536
+
537
+ function init() {
538
+ // Field navigation
539
+ $.nameField.addEventListener('return', () => $.emailField.focus())
540
+ $.emailField.addEventListener('return', () => $.passwordField.focus())
541
+ $.passwordField.addEventListener('return', () => $.confirmField.focus())
542
+ $.confirmField.addEventListener('return', onRegister)
543
+
544
+ // Real-time validation on blur
545
+ $.nameField.addEventListener('blur', () => validateField('name'))
546
+ $.emailField.addEventListener('blur', () => validateField('email'))
547
+ $.passwordField.addEventListener('blur', () => validateField('password'))
548
+ $.confirmField.addEventListener('blur', () => validateField('confirm'))
549
+ }
550
+
551
+ function validateField(field) {
552
+ const fields = {
553
+ name: { input: $.nameField, error: $.nameError },
554
+ email: { input: $.emailField, error: $.emailError },
555
+ password: { input: $.passwordField, error: $.passwordError },
556
+ confirm: { input: $.confirmField, error: $.confirmError }
557
+ }
558
+
559
+ const { input, error } = fields[field]
560
+ const value = input.value
561
+
562
+ let errorMsg
563
+ if (field === 'confirm') {
564
+ errorMsg = validators[field](value, $.passwordField.value)
565
+ } else {
566
+ errorMsg = validators[field](value)
567
+ }
568
+
569
+ if (errorMsg) {
570
+ error.applyProperties({ text: errorMsg, visible: true })
571
+ input.applyProperties({ borderColor: '#ef4444' })
572
+ return false
573
+ } else {
574
+ error.visible = false
575
+ input.applyProperties({ borderColor: '#d1d5db' })
576
+ return true
577
+ }
578
+ }
579
+
580
+ function validateAll() {
581
+ const nameValid = validateField('name')
582
+ const emailValid = validateField('email')
583
+ const passwordValid = validateField('password')
584
+ const confirmValid = validateField('confirm')
585
+
586
+ if (!$.termsSwitch.value) {
587
+ Ti.UI.createAlertDialog({
588
+ title: L('error'),
589
+ message: L('error_accept_terms')
590
+ }).show()
591
+ return false
592
+ }
593
+
594
+ return nameValid && emailValid && passwordValid && confirmValid
595
+ }
596
+
597
+ async function onRegister() {
598
+ if (!validateAll()) return
599
+
600
+ setLoading(true)
601
+
602
+ try {
603
+ await AuthService.register({
604
+ name: $.nameField.value.trim(),
605
+ email: $.emailField.value.trim().toLowerCase(),
606
+ password: $.passwordField.value
607
+ })
608
+
609
+ // Success - navigate to home
610
+ Navigation.replace('main')
611
+
612
+ } catch (error) {
613
+ Ti.UI.createAlertDialog({
614
+ title: L('error'),
615
+ message: error.message
616
+ }).show()
617
+ } finally {
618
+ setLoading(false)
619
+ }
620
+ }
621
+
622
+ function setLoading(loading) {
623
+ $.registerBtn.applyProperties({
624
+ enabled: !loading,
625
+ title: loading ? L('registering') : L('register')
626
+ })
627
+ }
628
+
629
+ function goToLogin() {
630
+ Navigation.back()
631
+ }
632
+
633
+ function cleanup() {
634
+ $.destroy()
635
+ }
636
+
637
+ $.cleanup = cleanup
638
+ ```
639
+
640
+ ## Tab-Based Navigation Example
641
+
642
+ ```xml
643
+ <!-- views/main.xml -->
644
+ <Alloy>
645
+ <TabGroup id="tabGroup" class="tabs-bg-white active-tint-blue-500">
646
+
647
+ <Tab id="homeTab" title="L('home')" icon="/images/icons/home.png">
648
+ <Require src="tabs/home" />
649
+ </Tab>
650
+
651
+ <Tab id="searchTab" title="L('search')" icon="/images/icons/search.png">
652
+ <Require src="tabs/search" />
653
+ </Tab>
654
+
655
+ <Tab id="cartTab" title="L('cart')" icon="/images/icons/cart.png">
656
+ <Require src="tabs/cart" />
657
+ </Tab>
658
+
659
+ <Tab id="profileTab" title="L('profile')" icon="/images/icons/profile.png">
660
+ <Require src="tabs/profile" />
661
+ </Tab>
662
+ </TabGroup>
663
+ </Alloy>
664
+ ```
665
+
666
+ ```javascript
667
+ // controllers/main.js
668
+ const EventBus = require('lib/services/eventBus')
669
+ const Events = EventBus.Events
670
+
671
+ function init() {
672
+ // Listen for cart updates to show badge
673
+ EventBus.on(Events.CART_UPDATED, onCartUpdated)
674
+
675
+ // Track tab changes
676
+ $.tabGroup.addEventListener('focus', onTabFocus)
677
+ }
678
+
679
+ function onTabFocus(e) {
680
+ // Analytics tracking
681
+ const tabNames = ['home', 'search', 'cart', 'profile']
682
+ Ti.Analytics.featureEvent(`tab:${tabNames[e.index]}`)
683
+ }
684
+
685
+ function onCartUpdated({ itemCount }) {
686
+ // Update badge on cart tab
687
+ $.cartTab.badge = itemCount > 0 ? String(itemCount) : null
688
+ }
689
+
690
+ // Switch to specific tab programmatically
691
+ function switchToTab(tabName) {
692
+ const tabMap = { home: 0, search: 1, cart: 2, profile: 3 }
693
+ const index = tabMap[tabName]
694
+
695
+ if (index !== undefined) {
696
+ $.tabGroup.activeTab = $.tabGroup.tabs[index]
697
+ }
698
+ }
699
+
700
+ // Export for external access
701
+ $.switchToTab = switchToTab
702
+
703
+ function cleanup() {
704
+ EventBus.off(Events.CART_UPDATED, onCartUpdated)
705
+ $.tabGroup.removeEventListener('focus', onTabFocus)
706
+ $.destroy()
707
+ }
708
+
709
+ $.cleanup = cleanup
710
+ ```
711
+
712
+ ```javascript
713
+ // controllers/tabs/home.js
714
+ // Each tab is a separate controller with its own lifecycle
715
+ function init() {
716
+ loadFeaturedProducts()
717
+
718
+ // Handle window focus (tab selected)
719
+ $.getView().addEventListener('focus', onFocus)
720
+ }
721
+
722
+ function onFocus() {
723
+ // Refresh data when returning to this tab
724
+ if (shouldRefresh()) {
725
+ loadFeaturedProducts()
726
+ }
727
+ }
728
+
729
+ function cleanup() {
730
+ $.getView().removeEventListener('focus', onFocus)
731
+ $.destroy()
732
+ }
733
+
734
+ $.cleanup = cleanup
735
+ ```