@maccesar/titools 4.1.0 → 4.4.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 (102) hide show
  1. package/README.md +30 -7
  2. package/bin/titools.js +4 -4
  3. package/commands/ti-audit.md +13 -0
  4. package/commands/ti-check.md +15 -0
  5. package/commands/ti-new-screen.md +18 -0
  6. package/lib/claude-plugin.js +105 -0
  7. package/lib/cleanup.js +18 -0
  8. package/lib/commands/doctor.js +75 -3
  9. package/lib/commands/skills.js +66 -6
  10. package/lib/commands/uninstall.js +32 -2
  11. package/lib/commands/update.js +15 -0
  12. package/lib/config.js +37 -0
  13. package/lib/installer.js +85 -1
  14. package/lib/symlink.js +40 -3
  15. package/package.json +2 -1
  16. package/skills/alloy-guides/references/MODELS.md +23 -0
  17. package/skills/alloy-guides/references/MODELS_ADVANCED.md +19 -0
  18. package/skills/alloy-guides/references/VIEWS_STYLES.md +12 -0
  19. package/skills/alloy-guides/references/VIEWS_XML.md +18 -0
  20. package/skills/purgetss/references/EXAMPLES.md +24 -0
  21. package/skills/purgetss/references/animation-advanced.md +12 -0
  22. package/skills/purgetss/references/animation-system.md +23 -0
  23. package/skills/purgetss/references/app-branding.md +23 -0
  24. package/skills/purgetss/references/apply-directive.md +17 -0
  25. package/skills/purgetss/references/arbitrary-values.md +13 -0
  26. package/skills/purgetss/references/class-index-properties.md +11 -0
  27. package/skills/purgetss/references/cli-commands.md +27 -0
  28. package/skills/purgetss/references/customization-deep-dive.md +15 -0
  29. package/skills/purgetss/references/dynamic-component-creation.md +16 -0
  30. package/skills/purgetss/references/migration-guide.md +20 -0
  31. package/skills/purgetss/references/multi-density-images.md +27 -0
  32. package/skills/purgetss/references/semantic-colors.md +15 -0
  33. package/skills/purgetss/references/tikit-components.md +17 -0
  34. package/skills/purgetss/references/titanium-resets.md +16 -0
  35. package/skills/purgetss/references/ui-ux-design.md +20 -0
  36. package/skills/ti-api/references/api-android.md +23 -0
  37. package/skills/ti-api/references/api-app-platform.md +24 -0
  38. package/skills/ti-api/references/api-core.md +24 -0
  39. package/skills/ti-api/references/api-data-network.md +21 -0
  40. package/skills/ti-api/references/api-media.md +15 -1
  41. package/skills/ti-api/references/api-modules-ble-bluetooth.md +26 -0
  42. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +18 -0
  43. package/skills/ti-api/references/api-modules-map.md +21 -2
  44. package/skills/ti-api/references/api-modules-nfc.md +34 -0
  45. package/skills/ti-api/references/api-modules-social-misc.md +22 -0
  46. package/skills/ti-api/references/api-services.md +21 -1
  47. package/skills/ti-api/references/api-ui-android.md +18 -0
  48. package/skills/ti-api/references/api-ui-extras.md +19 -1
  49. package/skills/ti-api/references/api-ui-ios-animator.md +14 -0
  50. package/skills/ti-api/references/api-ui-ios.md +42 -0
  51. package/skills/ti-api/references/api-ui-lists.md +14 -2
  52. package/skills/ti-api/references/api-ui-text-input.md +15 -0
  53. package/skills/ti-api/references/api-ui-views.md +19 -0
  54. package/skills/ti-api/references/api-ui-windows-navigation.md +13 -0
  55. package/skills/ti-api/references/api-xml-global.md +31 -0
  56. package/skills/ti-expert/SKILL.md +38 -5
  57. package/skills/ti-expert/references/adaptive-layouts.md +14 -0
  58. package/skills/ti-expert/references/alloy-builtins.md +26 -22
  59. package/skills/ti-expert/references/alloy-structure.md +57 -18
  60. package/skills/ti-expert/references/anti-patterns.md +13 -5
  61. package/skills/ti-expert/references/code-conventions.md +28 -3
  62. package/skills/ti-expert/references/controller-patterns.md +51 -26
  63. package/skills/ti-expert/references/error-handling.md +16 -0
  64. package/skills/ti-expert/references/examples.md +65 -21
  65. package/skills/ti-expert/references/feedback-migration.md +165 -0
  66. package/skills/ti-expert/references/feedback-surfaces.md +140 -0
  67. package/skills/ti-expert/references/feedback-widget-contracts.md +213 -0
  68. package/skills/ti-expert/references/migration-patterns.md +61 -24
  69. package/skills/ti-expert/references/patterns.md +16 -0
  70. package/skills/ti-expert/references/performance-optimization.md +17 -0
  71. package/skills/ti-expert/references/security-device.md +71 -56
  72. package/skills/ti-expert/references/state-management.md +21 -5
  73. package/skills/ti-expert/references/testing-e2e-ci.md +17 -0
  74. package/skills/ti-expert/references/testing-unit.md +20 -0
  75. package/skills/ti-expert/references/theming.md +135 -373
  76. package/skills/ti-guides/references/app-distribution.md +34 -0
  77. package/skills/ti-guides/references/application-frameworks.md +13 -0
  78. package/skills/ti-guides/references/cli-reference.md +16 -0
  79. package/skills/ti-guides/references/hyperloop-native-access.md +21 -0
  80. package/skills/ti-guides/references/javascript-primer.md +12 -0
  81. package/skills/ti-guides/references/tiapp-config.md +15 -0
  82. package/skills/ti-howtos/references/android-platform-deep-dives.md +15 -0
  83. package/skills/ti-howtos/references/cross-platform-development.md +16 -0
  84. package/skills/ti-howtos/references/debugging-profiling.md +15 -0
  85. package/skills/ti-howtos/references/extending-titanium.md +14 -0
  86. package/skills/ti-howtos/references/ios-platform-deep-dives.md +16 -0
  87. package/skills/ti-howtos/references/local-data-sources.md +32 -0
  88. package/skills/ti-howtos/references/notification-services.md +12 -0
  89. package/skills/ti-howtos/references/remote-data-sources.md +15 -0
  90. package/skills/ti-howtos/references/tutorials.md +13 -0
  91. package/skills/ti-ui/references/animation-and-matrices.md +17 -0
  92. package/skills/ti-ui/references/application-structures.md +19 -0
  93. package/skills/ti-ui/references/custom-fonts-styling.md +18 -0
  94. package/skills/ti-ui/references/event-handling.md +16 -0
  95. package/skills/ti-ui/references/gestures.md +17 -0
  96. package/skills/ti-ui/references/icons-and-splash-screens.md +17 -0
  97. package/skills/ti-ui/references/layouts-and-positioning.md +16 -0
  98. package/skills/ti-ui/references/listviews-and-performance.md +21 -0
  99. package/skills/ti-ui/references/orientation.md +20 -0
  100. package/skills/ti-ui/references/platform-ui-android.md +20 -0
  101. package/skills/ti-ui/references/platform-ui-ios.md +17 -0
  102. package/skills/ti-ui/references/tableviews.md +24 -0
@@ -1,5 +1,24 @@
1
1
  # TSS styling & code conventions
2
2
 
3
+ <!-- TOC-START -->
4
+ ## Contents
5
+
6
+ - [Module system: CommonJS (NOT ES6 modules)](#module-system-commonjs-not-es6-modules)
7
+ - [ES6+ features (Non-Module)](#es6-features-non-module)
8
+ - [No semicolons](#no-semicolons)
9
+ - [Styling with TSS](#styling-with-tss)
10
+ - [Platform & device modifiers](#platform-device-modifiers)
11
+ - [applyProperties() pattern](#applyproperties-pattern)
12
+ - [ListView templates](#listview-templates)
13
+ - [Memory cleanup](#memory-cleanup)
14
+ - [i18n and accessibility (a11y)](#i18n-and-accessibility-a11y)
15
+ - [Percentage-based layouts](#percentage-based-layouts)
16
+ - [Naming conventions](#naming-conventions)
17
+ - [Import/Export patterns](#importexport-patterns)
18
+ - [Async/Promise patterns](#asyncpromise-patterns)
19
+
20
+ <!-- TOC-END -->
21
+
3
22
  ## Module system: CommonJS (NOT ES6 modules)
4
23
 
5
24
  Titanium uses **CommonJS** for modules. Do NOT use ES6 `import`/`export`:
@@ -35,9 +54,15 @@ const name = user.name
35
54
 
36
55
  ## Styling with TSS
37
56
 
57
+ First detect `purgetss/` or `purgetss/config.cjs`. If present, invoke the
58
+ `purgetss` skill, use verified utilities, and do not edit generated
59
+ `app/styles/app.tss`. The manual TSS organization below applies when PurgeTSS is
60
+ not present or to specific properties for which that skill confirms no utility.
61
+
38
62
  ### TSS file organization
39
63
 
40
- Every controller can have a matching TSS file. Alloy also supports a global `app.tss`.
64
+ Every controller can have a matching TSS file. Standard Alloy also supports a
65
+ manually maintained global `app.tss`.
41
66
 
42
67
  ```
43
68
  app/styles/
@@ -76,7 +101,7 @@ app/styles/
76
101
  | Selector | Use for | Example |
77
102
  | ------------------- | --------------------------- | ---------------------- |
78
103
  | Element (`"Label"`) | App-wide defaults | Base font, color |
79
- | Class (`".card"`) | Reusable patterns (3+ uses) | Cards, buttons, inputs |
104
+ | Class (`".card"`) | Reusable visual pattern | Cards, buttons, inputs |
80
105
  | ID (`"#name"`) | Unique element positioning | Specific layout needs |
81
106
 
82
107
  ### Class selectors in XML
@@ -108,7 +133,7 @@ When an element has both an ID and classes, properties merge with ID taking prio
108
133
  <Button id="saveButton" class="btn-primary" title="L('save')" />
109
134
  ```
110
135
 
111
- ### Building a design system in app.tss
136
+ ### Building a standard Alloy design system in app.tss
112
137
 
113
138
  Define reusable style classes for consistency:
114
139
 
@@ -1,5 +1,23 @@
1
1
  # Alloy controller patterns
2
2
 
3
+ <!-- TOC-START -->
4
+ ## Contents
5
+
6
+ - [Titanium layout rules](#titanium-layout-rules)
7
+ - [Animation usage](#animation-usage)
8
+ - [Dynamic styling](#dynamic-styling)
9
+ - [Percentage-based layouts](#percentage-based-layouts)
10
+ - [Controller lifecycle patterns](#controller-lifecycle-patterns)
11
+ - [Form handling patterns](#form-handling-patterns)
12
+ - [Loading state patterns](#loading-state-patterns)
13
+ - [Modal and dialog patterns](#modal-and-dialog-patterns)
14
+ - [Tabgroup pattern](#tabgroup-pattern)
15
+ - [Navigationwindow pattern (iOS)](#navigationwindow-pattern-ios)
16
+ - [Searchbar with ListView](#searchbar-with-listview)
17
+ - [Pull-to-refresh pattern](#pull-to-refresh-pattern)
18
+
19
+ <!-- TOC-END -->
20
+
3
21
  ## Titanium layout rules
4
22
 
5
23
  - Use `layout: 'horizontal'` for left-to-right
@@ -263,43 +281,42 @@ async function loadData() {
263
281
 
264
282
  ## Modal and dialog patterns
265
283
 
284
+ Declare the app-owned `dialog` and `bottomSheet` Widgets in the window XML.
285
+ Attach them after the host exists and destroy them from the controller cleanup.
286
+ Keep a modal Window for a distinct multi-step task, not for ordinary feedback.
287
+
266
288
  ```javascript
267
- // AlertDialog
289
+ function attachFeedback(hostWindow) {
290
+ $.dialog.attach(hostWindow)
291
+ $.bottomSheet.attach(hostWindow)
292
+ }
293
+
294
+ // Critical, irreversible confirmation
268
295
  function showConfirmDelete(itemName) {
269
- const dialog = Ti.UI.createAlertDialog({
296
+ $.dialog.show({
270
297
  title: L('confirm_delete'),
271
298
  message: String.format(L('delete_message'), itemName),
272
- buttonNames: [L('cancel'), L('delete')],
273
- cancel: 0,
274
- destructive: 1 // iOS: red button
299
+ cancelTitle: L('cancel'),
300
+ confirmTitle: L('delete'),
301
+ destructive: true,
302
+ tone: 'warning',
303
+ onConfirm: performDelete
275
304
  })
276
-
277
- dialog.addEventListener('click', (e) => {
278
- if (e.index === 1) {
279
- performDelete()
280
- }
281
- })
282
-
283
- dialog.show()
284
305
  }
285
306
 
286
- // OptionDialog (Action Sheet)
307
+ // Contextual choices
287
308
  function showSortOptions() {
288
- const options = [L('sort_name'), L('sort_date'), L('sort_price')]
309
+ const items = [
310
+ { id: 'name', title: L('sort_name') },
311
+ { id: 'date', title: L('sort_date') },
312
+ { id: 'price', title: L('sort_price') }
313
+ ]
289
314
 
290
- const dialog = Ti.UI.createOptionDialog({
315
+ $.bottomSheet.show({
291
316
  title: L('sort_by'),
292
- options: [...options, L('cancel')],
293
- cancel: options.length
294
- })
295
-
296
- dialog.addEventListener('click', (e) => {
297
- if (e.index < options.length) {
298
- applySortOption(e.index)
299
- }
317
+ items,
318
+ onSelect: item => applySortOption(item.id)
300
319
  })
301
-
302
- dialog.show()
303
320
  }
304
321
 
305
322
  // Modal Window
@@ -323,6 +340,14 @@ function openModal(route, params) {
323
340
 
324
341
  return controller
325
342
  }
343
+
344
+ function cleanup() {
345
+ $.dialog.destroy()
346
+ $.bottomSheet.destroy()
347
+ $.destroy()
348
+ }
349
+
350
+ $.cleanup = cleanup
326
351
  ```
327
352
 
328
353
  ## Tabgroup pattern
@@ -1,5 +1,21 @@
1
1
  # Error handling & logging guide
2
2
 
3
+ <!-- TOC-START -->
4
+ ## Contents
5
+
6
+ - [Apperror classes](#apperror-classes)
7
+ - [Logger service](#logger-service)
8
+ - [Error handler service](#error-handler-service)
9
+ - [Using the logger](#using-the-logger)
10
+ - [API client error handling](#api-client-error-handling)
11
+ - [Controller error handling pattern](#controller-error-handling-pattern)
12
+ - [Global error handler](#global-error-handler)
13
+ - [Crash reporting integration](#crash-reporting-integration)
14
+ - [Validation helper](#validation-helper)
15
+ - [Logging best practices](#logging-best-practices)
16
+
17
+ <!-- TOC-END -->
18
+
3
19
  ## Apperror classes
4
20
 
5
21
  ```javascript
@@ -1,5 +1,36 @@
1
1
  # Alloy implementation examples
2
2
 
3
+ <!-- TOC-START -->
4
+ ## Contents
5
+
6
+ - [Reading these examples](#reading-these-examples)
7
+ - [API client service](#api-client-service)
8
+ - [Native module wrapper service](#native-module-wrapper-service)
9
+ - [i18n helper](#i18n-helper)
10
+ - [Model with SQL adapter](#model-with-sql-adapter)
11
+ - [Fully styled & accessible view](#fully-styled-accessible-view)
12
+ - [Cleanup pattern in controller](#cleanup-pattern-in-controller)
13
+ - [Animation usage](#animation-usage)
14
+ - [Complete crud example](#complete-crud-example)
15
+ - [ListView with search and filter](#listview-with-search-and-filter)
16
+ - [Form with validation example](#form-with-validation-example)
17
+ - [Tab-based navigation example](#tab-based-navigation-example)
18
+
19
+ <!-- TOC-END -->
20
+
21
+ ## Reading these examples
22
+
23
+ Snippets that call `$.dialog`, `$.bottomSheet`, or `$.snackbar` expect the app to
24
+ own those surfaces as Alloy Widgets under `app/widgets/`. Their public API,
25
+ queueing, and cleanup rules live in [Feedback Widget Contracts](feedback-widget-contracts.md);
26
+ [Feedback Surfaces](feedback-surfaces.md) covers which surface a given event
27
+ deserves. The `<Widget>` declaration alone will not compile until those Widgets
28
+ exist and are registered in `app/config.json`.
29
+
30
+ Styles below carry literal hex values so each example reads on its own. Production
31
+ code should name semantic color roles instead, so light and dark resolve without a
32
+ second code path — see [Theming and dark mode](theming.md).
33
+
3
34
  ## API client service
4
35
  Standard logic for network requests.
5
36
 
@@ -333,6 +364,8 @@ $.cleanup = cleanup
333
364
  <ImageView id="emptyIcon" image="/images/search.png" />
334
365
  <Label id="emptyLabel" text="L('no_results')" />
335
366
  </View>
367
+
368
+ <Widget id="bottomSheet" src="com.example.ui.bottomsheet" />
336
369
  </Window>
337
370
  </Alloy>
338
371
  ```
@@ -358,6 +391,7 @@ let searchQuery = ''
358
391
  let debounceTimer = null
359
392
 
360
393
  function init() {
394
+ $.bottomSheet.attach($.contactsWindow)
361
395
  loadData()
362
396
 
363
397
  $.searchBar.addEventListener('cancel', clearSearch)
@@ -378,21 +412,23 @@ function clearSearch() {
378
412
  }
379
413
 
380
414
  function showFilters() {
381
- const dialog = Ti.UI.createOptionDialog({
415
+ const items = [
416
+ { id: 'all', title: L('all') },
417
+ { id: 'work', title: L('work') },
418
+ { id: 'personal', title: L('personal') },
419
+ { id: 'family', title: L('family') }
420
+ ]
421
+
422
+ $.bottomSheet.show({
382
423
  title: L('filter_by_category'),
383
- options: ['All', 'Work', 'Personal', 'Family', L('cancel')],
384
- cancel: 4
385
- })
386
-
387
- dialog.addEventListener('click', (e) => {
388
- if (e.index < 4) {
389
- activeFilters.category = e.index === 0 ? null : ['work', 'personal', 'family'][e.index - 1]
424
+ items,
425
+ selectedId: activeFilters.category || 'all',
426
+ onSelect: item => {
427
+ activeFilters.category = item.id === 'all' ? null : item.id
390
428
  updateFilterTags()
391
429
  applyFilters()
392
430
  }
393
431
  })
394
-
395
- dialog.show()
396
432
  }
397
433
 
398
434
  function applyFilters() {
@@ -448,6 +484,7 @@ function createFilterTag(label, onRemove) {
448
484
 
449
485
  function cleanup() {
450
486
  clearTimeout(debounceTimer)
487
+ $.bottomSheet.destroy()
451
488
  $.searchBar.removeEventListener('change', onSearchChange)
452
489
  $.destroy()
453
490
  }
@@ -513,6 +550,7 @@ $.cleanup = cleanup
513
550
  <Switch id="termsSwitch" />
514
551
  <Label id="termsLabel" text="L('accept_terms')" />
515
552
  </View>
553
+ <Label id="termsError" />
516
554
 
517
555
  <!-- Register Button -->
518
556
  <Button id="registerBtn"
@@ -527,6 +565,8 @@ $.cleanup = cleanup
527
565
  </View>
528
566
  </View>
529
567
  </ScrollView>
568
+
569
+ <Widget id="dialog" src="com.example.ui.dialog" />
530
570
  </Window>
531
571
  </Alloy>
532
572
  ```
@@ -553,6 +593,7 @@ $.cleanup = cleanup
553
593
  "#termsRow": { layout: 'horizontal', left: 16, right: 16, top: 24, width: Ti.UI.FILL }
554
594
  "#termsSwitch": { width: 48 }
555
595
  "#termsLabel": { left: 8 }
596
+ "#termsError": { left: 16, right: 16, top: 4, font: { fontSize: 12 }, color: '#ef4444', visible: false }
556
597
  "#registerBtn": { left: 16, right: 16, top: 24, height: 56, borderRadius: 12, backgroundColor: '#2563eb', color: '#fff', font: { fontWeight: 'bold' } }
557
598
  "#loginRow": { layout: 'horizontal', top: 16 }
558
599
  "#haveAccountLabel": { font: { fontSize: 14 }, color: '#4b5563' }
@@ -594,6 +635,8 @@ const validators = {
594
635
  }
595
636
 
596
637
  function init() {
638
+ $.dialog.attach($.registerWindow)
639
+
597
640
  // Field navigation
598
641
  $.nameField.addEventListener('return', () => $.emailField.focus())
599
642
  $.emailField.addEventListener('return', () => $.passwordField.focus())
@@ -641,16 +684,14 @@ function validateAll() {
641
684
  const emailValid = validateField('email')
642
685
  const passwordValid = validateField('password')
643
686
  const confirmValid = validateField('confirm')
687
+ const termsValid = $.termsSwitch.value
644
688
 
645
- if (!$.termsSwitch.value) {
646
- Ti.UI.createAlertDialog({
647
- title: L('error'),
648
- message: L('error_accept_terms')
649
- }).show()
650
- return false
651
- }
689
+ $.termsError.applyProperties({
690
+ text: termsValid ? '' : L('error_accept_terms'),
691
+ visible: !termsValid
692
+ })
652
693
 
653
- return nameValid && emailValid && passwordValid && confirmValid
694
+ return nameValid && emailValid && passwordValid && confirmValid && termsValid
654
695
  }
655
696
 
656
697
  async function onRegister() {
@@ -669,10 +710,12 @@ async function onRegister() {
669
710
  Navigation.replace('main')
670
711
 
671
712
  } catch (error) {
672
- Ti.UI.createAlertDialog({
713
+ $.dialog.show({
673
714
  title: L('error'),
674
- message: error.message
675
- }).show()
715
+ message: error.message,
716
+ tone: 'error',
717
+ confirmTitle: L('close')
718
+ })
676
719
  } finally {
677
720
  setLoading(false)
678
721
  }
@@ -690,6 +733,7 @@ function goToLogin() {
690
733
  }
691
734
 
692
735
  function cleanup() {
736
+ $.dialog.destroy()
693
737
  $.destroy()
694
738
  }
695
739
 
@@ -0,0 +1,165 @@
1
+ # Migrating feedback surfaces
2
+
3
+ Migrate semantics, not proxy names. Replacing every native dialog with the same
4
+ custom modal preserves the original design problem.
5
+
6
+ ## Contents
7
+
8
+ - [Inventory](#inventory)
9
+ - [Classify before replacing](#classify-before-replacing)
10
+ - [Migrate one window at a time](#migrate-one-window-at-a-time)
11
+ - [Deliberate silence](#deliberate-silence)
12
+ - [Contract tests](#contract-tests)
13
+ - [Delivery verification](#delivery-verification)
14
+ - [When to share Widgets between apps](#when-to-share-widgets-between-apps)
15
+
16
+ ## Inventory
17
+
18
+ Search the application for:
19
+
20
+ ```bash
21
+ rg -n "createAlertDialog|createOptionDialog|createNotification|Toast|toast|snackbar|overlay" app/
22
+ ```
23
+
24
+ Also inspect helpers that wrap these calls, XML overlays, controller timers,
25
+ global events, and callbacks that can present UI indirectly. Record for every
26
+ occurrence:
27
+
28
+ - trigger and current message;
29
+ - success, information, warning, error, confirmation, or choice;
30
+ - blocking versus non-blocking;
31
+ - reversible versus irreversible;
32
+ - app-owned versus system-owned;
33
+ - callback side effects and cancellation behavior;
34
+ - owning window and cleanup path.
35
+
36
+ Do not implement the replacement until every occurrence has a semantic target
37
+ from `feedback-surfaces.md`.
38
+
39
+ ## Classify before replacing
40
+
41
+ Use these defaults:
42
+
43
+ - ordinary success and undoable actions → Snackbar;
44
+ - recoverable field validation → inline validation;
45
+ - persistent app condition → Banner or inline notice;
46
+ - blocking failure or required acknowledgement → Dialog;
47
+ - uncommon irreversible decision → destructive Dialog;
48
+ - several related actions or app-data choices → Bottom Sheet;
49
+ - long structured content → scrollable sheet or dedicated screen;
50
+ - OS permission, picker, share, date/time, biometric, or purchase flow → keep the
51
+ system surface.
52
+
53
+ Preserve domain behavior separately from presentation. Services return results
54
+ or errors; the window/controller that owns visible context chooses the surface.
55
+ Do not let a service create app-owned UI, especially before the root window
56
+ exists.
57
+
58
+ ## Migrate one window at a time
59
+
60
+ 1. Register the required Widget dependencies in `app/config.json`.
61
+ 2. Add the Widgets as the final visual children of the window XML.
62
+ 3. Attach modal Widgets after the window exists.
63
+ 4. Route each old result to its classified surface.
64
+ 5. Call Widget `destroy()` from the window close cleanup.
65
+ 6. Exercise success, failure, cancel, Back, backdrop, double tap, and consecutive
66
+ presentation paths.
67
+ 7. Remove the old dialog, local overlay, helper, timer, and listener after the
68
+ migrated window passes verification.
69
+ 8. Commit the completed vertical slice before moving to the next window.
70
+
71
+ Do not keep commented legacy implementations. Version control is the rollback
72
+ mechanism; commented duplicates hide incomplete migrations and stale callbacks.
73
+
74
+ ## Deliberate silence
75
+
76
+ Cancellation or an unknowable result can be a complete, intentional outcome.
77
+ Model it explicitly and do not infer a message from falsy values.
78
+
79
+ Example contract for an exported file:
80
+
81
+ ```javascript
82
+ function handleSaveResult(error, result) {
83
+ if (error) {
84
+ $.dialog.show({
85
+ title: L('error'),
86
+ message: error.message,
87
+ tone: 'error',
88
+ confirmTitle: L('close')
89
+ })
90
+ return
91
+ }
92
+
93
+ if (result.saved === true) {
94
+ $.snackbar.show({
95
+ message: L('file_saved'),
96
+ tone: 'success'
97
+ })
98
+ }
99
+
100
+ // saved === false: Android picker was cancelled; the internal original remains.
101
+ // saved === null: iOS does not report whether the person completed the save.
102
+ // Both outcomes are deliberately silent.
103
+ }
104
+ ```
105
+
106
+ Do not add "Cancelled" for `saved === false`. Do not claim success for
107
+ `saved === null`. Cover both branches with tests so a future fallback does not
108
+ erase the intended silence.
109
+
110
+ ## Contract tests
111
+
112
+ Add repository-appropriate static or unit tests for:
113
+
114
+ - all Widget folders, manifests, XML views, controllers, and dependency entries;
115
+ - each public method documented in `feedback-widget-contracts.md`;
116
+ - the expected Widget declared in every migrated window;
117
+ - absence of app-owned `Ti.UI.createAlertDialog` and
118
+ `Ti.UI.createOptionDialog` defaults;
119
+ - removal of the previous local Snackbar/overlay and timer;
120
+ - Snackbar replacement and timer reset;
121
+ - Dialog and Bottom Sheet FIFO ordering;
122
+ - exactly-once callbacks under double taps and competing close paths;
123
+ - Android Back and backdrop policy;
124
+ - cleanup of active and queued presentations;
125
+ - explicit silence for cancel and indeterminate save results.
126
+
127
+ Do not enforce blanket bans on native system surfaces. A remaining native call
128
+ must be tied to an OS-owned interaction or documented fallback.
129
+
130
+ ## Delivery verification
131
+
132
+ Run the project's real commands rather than assuming names. A complete migration
133
+ normally includes:
134
+
135
+ 1. lint with zero errors and no new warnings;
136
+ 2. unit and contract tests with no new failures;
137
+ 3. PurgeTSS generation when detected, followed by inspection of unused or
138
+ unsupported classes at the end of `app.tss`;
139
+ 4. Alloy compilation for Android and iOS;
140
+ 5. native builds for every supported platform;
141
+ 6. manual checks in light, dark, and system appearance;
142
+ 7. phone and tablet/large-screen checks;
143
+ 8. Back, backdrop, cancel, double tap, long list, and long localized text checks;
144
+ 9. VoiceOver and TalkBack focus/traversal checks;
145
+ 10. consecutive presentation and window-close cleanup checks.
146
+
147
+ Record pre-existing failures before migration and require the final run to add
148
+ none. Build artifacts are not proof of usable modal behavior; perform the manual
149
+ interaction checks on device or simulator.
150
+
151
+ ## When to share Widgets between apps
152
+
153
+ Extract or copy a Widget into another app only when:
154
+
155
+ - its API is stable and contains no business rules;
156
+ - localized strings and callbacks are supplied by the host;
157
+ - appearance depends on a documented semantic-token contract;
158
+ - platform and minimum Titanium/Alloy versions are declared;
159
+ - lifecycle and accessibility tests travel with it;
160
+ - at least two apps need the same behavior, or one app has a clear portability
161
+ requirement.
162
+
163
+ Do not extract merely because a view appeared three times. Extract because the
164
+ boundary is independently understandable, testable, and maintainable.
165
+
@@ -0,0 +1,140 @@
1
+ # Feedback surfaces and app-owned UI
2
+
3
+ Use this guide before choosing a Titanium proxy or drawing a component. The
4
+ surface follows the meaning of the event: style comes afterwards.
5
+
6
+ ## Contents
7
+
8
+ - [Decision sequence](#decision-sequence)
9
+ - [Feedback scale](#feedback-scale)
10
+ - [App-owned versus system-owned](#app-owned-versus-system-owned)
11
+ - [What to standardize next](#what-to-standardize-next)
12
+ - [Phone, tablet, and accessibility](#phone-tablet-and-accessibility)
13
+ - [References](#references)
14
+
15
+ ## Decision sequence
16
+
17
+ Classify the event in this order:
18
+
19
+ 1. **Owner**: does the app own the interaction, or does the operating system?
20
+ 2. **Blocking**: can the person continue safely without acknowledging it?
21
+ 3. **Recovery**: is the action reversible or is data at risk?
22
+ 4. **Location**: does the message belong to a field, a screen, or the whole app?
23
+ 5. **Persistence**: is it transient, persistent until state changes, or modal?
24
+ 6. **Choice shape**: is there one acknowledgement, a binary decision, or a list
25
+ of related actions?
26
+
27
+ Do not begin with "which native dialog can display this string?" That turns
28
+ different meanings into the same interruption and makes the app feel visually
29
+ inconsistent.
30
+
31
+ ## Feedback scale
32
+
33
+ | Situation | Preferred surface | Why |
34
+ | --- | --- | --- |
35
+ | Ordinary success or confirmation | Snackbar | Visible without interrupting the task |
36
+ | Undoable action | Snackbar with one Undo action | Keeps recovery close to the event |
37
+ | Non-blocking warning | Warning Snackbar | Calls attention without forcing acknowledgement |
38
+ | Invalid value in one field | Inline validation next to that field | Preserves context and identifies the fix |
39
+ | Form-wide recoverable validation | Inline summary; Snackbar only if no single field owns it | Avoids a modal loop |
40
+ | Persistent connectivity, sync, or account state | Banner or inline notice | Remains visible while the condition remains true |
41
+ | Blocking error or required acknowledgement | Dialog | The task cannot continue safely |
42
+ | Uncommon irreversible action | Destructive confirmation Dialog | Provides a safe escape before data loss |
43
+ | Several actions related to the current context | Bottom Sheet | Presents a compact choice without pretending it is an error |
44
+ | Long, searchable, or multi-select options | Bottom Sheet, sheet, or dedicated screen with `ListView` | A dialog is too constrained |
45
+ | Multi-step task or complex form | Sheet or dedicated screen | The interaction has its own workflow |
46
+ | No content yet | Empty State inside the screen | This is screen state, not transient feedback |
47
+ | Initial or blocking work | Loading/Progress state in context | Communicates progress without inventing a message |
48
+
49
+ Reserve Dialogs for critical information. Do not use them merely because they
50
+ are easy to create. If an action is common and undoable, prefer immediate
51
+ execution plus Undo over confirmation.
52
+
53
+ ### Snackbar versus Toast
54
+
55
+ For app-owned foreground feedback, standardize on Snackbar. It supports the
56
+ app's semantic colors, accessibility, safe-area positioning, duration policy,
57
+ and one optional recovery action. Do not maintain both a custom Toast and a
58
+ Snackbar for the same job.
59
+
60
+ A platform Toast can remain only when the operating system owns the behavior or
61
+ when a documented platform fallback is necessary. It is not the default design
62
+ surface.
63
+
64
+ ### Banner versus Snackbar
65
+
66
+ - Use Snackbar when the event has happened and the message can disappear.
67
+ - Use Banner or inline notice while an ongoing condition remains relevant.
68
+ - Never keep a Snackbar indefinitely to simulate persistent screen state.
69
+
70
+ ### Bottom Sheet versus Dialog
71
+
72
+ - Use Bottom Sheet for related choices: sorting, exporting, selecting a category,
73
+ or choosing an action for an item.
74
+ - Use Dialog for acknowledgement or a consequential confirm/cancel decision.
75
+ - Move long prose, structured data, search, or complex input into a sheet or
76
+ dedicated screen.
77
+
78
+ ## App-owned versus system-owned
79
+
80
+ Recreate and brand a surface only when the app owns its content and behavior.
81
+ Keep the platform UI when the system owns trust, permissions, identity, files,
82
+ or another protected workflow.
83
+
84
+ | Keep native/system-owned | May be app-owned and styled |
85
+ | --- | --- |
86
+ | Permission prompts | Success, warning, and error feedback |
87
+ | File and document pickers | Destructive confirmations |
88
+ | Share sheets and activity views | Contextual action lists |
89
+ | Date/time pickers when the platform interaction is desired | Category and app-data selectors |
90
+ | Camera and media authorization UI | Loading, empty, and error states |
91
+ | Biometric authentication prompt | Banners and inline validation |
92
+ | Keyboard, autofill, purchases, notifications, and OS settings | App-specific onboarding or education |
93
+
94
+ An app may explain why a system surface is about to appear, but it must not
95
+ imitate the trusted system prompt. Use the official Titanium API or native module
96
+ for the system-owned part.
97
+
98
+ Cancellation is often a normal system outcome. Do not turn every cancellation
99
+ into an error, a "Cancelled" message, or an unsupported success claim. Document
100
+ silence explicitly at the call site when it is meaningful.
101
+
102
+ ## What to standardize next
103
+
104
+ After Snackbar, Dialog, and Bottom Sheet, the highest-value shared patterns are:
105
+
106
+ 1. **Banner / Inline Notice** for persistent app state.
107
+ 2. **Loading / Progress wrapper** that uses the native activity indicator but
108
+ standardizes placement, text, blocking behavior, and cleanup.
109
+ 3. **Empty / Error State** for consistent screen-level recovery.
110
+ 4. **Field Validation** for message placement, focus, accessibility, and colors.
111
+
112
+ Standardize buttons, cards, badges, chips, skeletons, and coach marks only when
113
+ real product repetition exposes a stable contract. A visual resemblance alone
114
+ does not justify a Widget.
115
+
116
+ ## Phone, tablet, and accessibility
117
+
118
+ - On phones, present contextual choices from the bottom and keep touch targets
119
+ reachable.
120
+ - On tablets and large screens, use a centered, width-limited action surface or
121
+ a platform-appropriate anchored presentation; do not stretch phone geometry
122
+ edge to edge.
123
+ - Respect safe areas, bottom navigation, orientation, split screen, text scaling,
124
+ and localized strings.
125
+ - Announce transient feedback appropriately and move accessibility focus into a
126
+ modal surface when it opens.
127
+ - While a modal is active, prevent interaction with and accessibility traversal
128
+ into the underlying content. Restore the exact previous state on close.
129
+ - Back, backdrop, Cancel, and destructive actions must have predictable semantics
130
+ and must never execute callbacks more than once.
131
+
132
+ ## References
133
+
134
+ - [Titanium Alloy Widgets](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Widgets.html)
135
+ - [Apple Human Interface Guidelines: Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts)
136
+ - [Apple Human Interface Guidelines: Modality](https://developer.apple.com/design/human-interface-guidelines/modality)
137
+ - [Material Design 3: Snackbar](https://m3.material.io/components/snackbar/overview)
138
+ - [Material Design 3: Dialogs](https://m3.material.io/components/dialogs/overview)
139
+ - [Material Design 3: Bottom sheets](https://m3.material.io/components/bottom-sheets/overview)
140
+