@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.
- package/README.md +30 -7
- package/bin/titools.js +4 -4
- package/commands/ti-audit.md +13 -0
- package/commands/ti-check.md +15 -0
- package/commands/ti-new-screen.md +18 -0
- package/lib/claude-plugin.js +105 -0
- package/lib/cleanup.js +18 -0
- package/lib/commands/doctor.js +75 -3
- package/lib/commands/skills.js +66 -6
- package/lib/commands/uninstall.js +32 -2
- package/lib/commands/update.js +15 -0
- package/lib/config.js +37 -0
- package/lib/installer.js +85 -1
- package/lib/symlink.js +40 -3
- package/package.json +2 -1
- package/skills/alloy-guides/references/MODELS.md +23 -0
- package/skills/alloy-guides/references/MODELS_ADVANCED.md +19 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +12 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +18 -0
- package/skills/purgetss/references/EXAMPLES.md +24 -0
- package/skills/purgetss/references/animation-advanced.md +12 -0
- package/skills/purgetss/references/animation-system.md +23 -0
- package/skills/purgetss/references/app-branding.md +23 -0
- package/skills/purgetss/references/apply-directive.md +17 -0
- package/skills/purgetss/references/arbitrary-values.md +13 -0
- package/skills/purgetss/references/class-index-properties.md +11 -0
- package/skills/purgetss/references/cli-commands.md +27 -0
- package/skills/purgetss/references/customization-deep-dive.md +15 -0
- package/skills/purgetss/references/dynamic-component-creation.md +16 -0
- package/skills/purgetss/references/migration-guide.md +20 -0
- package/skills/purgetss/references/multi-density-images.md +27 -0
- package/skills/purgetss/references/semantic-colors.md +15 -0
- package/skills/purgetss/references/tikit-components.md +17 -0
- package/skills/purgetss/references/titanium-resets.md +16 -0
- package/skills/purgetss/references/ui-ux-design.md +20 -0
- package/skills/ti-api/references/api-android.md +23 -0
- package/skills/ti-api/references/api-app-platform.md +24 -0
- package/skills/ti-api/references/api-core.md +24 -0
- package/skills/ti-api/references/api-data-network.md +21 -0
- package/skills/ti-api/references/api-media.md +15 -1
- package/skills/ti-api/references/api-modules-ble-bluetooth.md +26 -0
- package/skills/ti-api/references/api-modules-coremotion-urlsession.md +18 -0
- package/skills/ti-api/references/api-modules-map.md +21 -2
- package/skills/ti-api/references/api-modules-nfc.md +34 -0
- package/skills/ti-api/references/api-modules-social-misc.md +22 -0
- package/skills/ti-api/references/api-services.md +21 -1
- package/skills/ti-api/references/api-ui-android.md +18 -0
- package/skills/ti-api/references/api-ui-extras.md +19 -1
- package/skills/ti-api/references/api-ui-ios-animator.md +14 -0
- package/skills/ti-api/references/api-ui-ios.md +42 -0
- package/skills/ti-api/references/api-ui-lists.md +14 -2
- package/skills/ti-api/references/api-ui-text-input.md +15 -0
- package/skills/ti-api/references/api-ui-views.md +19 -0
- package/skills/ti-api/references/api-ui-windows-navigation.md +13 -0
- package/skills/ti-api/references/api-xml-global.md +31 -0
- package/skills/ti-expert/SKILL.md +38 -5
- package/skills/ti-expert/references/adaptive-layouts.md +14 -0
- package/skills/ti-expert/references/alloy-builtins.md +26 -22
- package/skills/ti-expert/references/alloy-structure.md +57 -18
- package/skills/ti-expert/references/anti-patterns.md +13 -5
- package/skills/ti-expert/references/code-conventions.md +28 -3
- package/skills/ti-expert/references/controller-patterns.md +51 -26
- package/skills/ti-expert/references/error-handling.md +16 -0
- package/skills/ti-expert/references/examples.md +65 -21
- package/skills/ti-expert/references/feedback-migration.md +165 -0
- package/skills/ti-expert/references/feedback-surfaces.md +140 -0
- package/skills/ti-expert/references/feedback-widget-contracts.md +213 -0
- package/skills/ti-expert/references/migration-patterns.md +61 -24
- package/skills/ti-expert/references/patterns.md +16 -0
- package/skills/ti-expert/references/performance-optimization.md +17 -0
- package/skills/ti-expert/references/security-device.md +71 -56
- package/skills/ti-expert/references/state-management.md +21 -5
- package/skills/ti-expert/references/testing-e2e-ci.md +17 -0
- package/skills/ti-expert/references/testing-unit.md +20 -0
- package/skills/ti-expert/references/theming.md +135 -373
- package/skills/ti-guides/references/app-distribution.md +34 -0
- package/skills/ti-guides/references/application-frameworks.md +13 -0
- package/skills/ti-guides/references/cli-reference.md +16 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +21 -0
- package/skills/ti-guides/references/javascript-primer.md +12 -0
- package/skills/ti-guides/references/tiapp-config.md +15 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +15 -0
- package/skills/ti-howtos/references/cross-platform-development.md +16 -0
- package/skills/ti-howtos/references/debugging-profiling.md +15 -0
- package/skills/ti-howtos/references/extending-titanium.md +14 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +16 -0
- package/skills/ti-howtos/references/local-data-sources.md +32 -0
- package/skills/ti-howtos/references/notification-services.md +12 -0
- package/skills/ti-howtos/references/remote-data-sources.md +15 -0
- package/skills/ti-howtos/references/tutorials.md +13 -0
- package/skills/ti-ui/references/animation-and-matrices.md +17 -0
- package/skills/ti-ui/references/application-structures.md +19 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +18 -0
- package/skills/ti-ui/references/event-handling.md +16 -0
- package/skills/ti-ui/references/gestures.md +17 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +17 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +16 -0
- package/skills/ti-ui/references/listviews-and-performance.md +21 -0
- package/skills/ti-ui/references/orientation.md +20 -0
- package/skills/ti-ui/references/platform-ui-android.md +20 -0
- package/skills/ti-ui/references/platform-ui-ios.md +17 -0
- package/skills/ti-ui/references/tableviews.md +24 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Contracts for feedback Widgets
|
|
2
|
+
|
|
3
|
+
Use these contracts for app-owned Snackbar, Dialog, and Bottom Sheet surfaces in
|
|
4
|
+
Alloy. Namespaces and visual tokens belong to each app; behavior stays stable.
|
|
5
|
+
|
|
6
|
+
## Contents
|
|
7
|
+
|
|
8
|
+
- [Boundary and registration](#boundary-and-registration)
|
|
9
|
+
- [Snackbar](#snackbar)
|
|
10
|
+
- [Dialog](#dialog)
|
|
11
|
+
- [Bottom Sheet](#bottom-sheet)
|
|
12
|
+
- [Shared lifecycle contract](#shared-lifecycle-contract)
|
|
13
|
+
- [PurgeTSS integration](#purgetss-integration)
|
|
14
|
+
- [Widget, Require, state, or native UI](#widget-require-state-or-native-ui)
|
|
15
|
+
|
|
16
|
+
## Boundary and registration
|
|
17
|
+
|
|
18
|
+
Implement each surface as an independent Alloy Widget under `app/widgets/`.
|
|
19
|
+
Each Widget owns its XML, controller, optional styles, assets, state, listeners,
|
|
20
|
+
timers, and `widget.json`. Keep them independent from any third-party component
|
|
21
|
+
library — including TiKit, the Alloy/PurgeTSS component set. A feedback surface
|
|
22
|
+
must not require one to work, and must not be folded into one: its value comes
|
|
23
|
+
from being installable and testable on its own.
|
|
24
|
+
|
|
25
|
+
Register every Widget in `app/config.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"com.example.ui.bottomsheet": "1.0.0",
|
|
31
|
+
"com.example.ui.dialog": "1.0.0",
|
|
32
|
+
"com.example.ui.snackbar": "1.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use the app's reverse-domain namespace. The dependency key must match the `id`
|
|
38
|
+
in that Widget's `widget.json`.
|
|
39
|
+
|
|
40
|
+
Declare Widgets in XML, normally as the last visual children of the owning
|
|
41
|
+
window so overlays render above screen content:
|
|
42
|
+
|
|
43
|
+
```xml
|
|
44
|
+
<Widget id="snackbar" src="com.example.ui.snackbar" />
|
|
45
|
+
<Widget id="dialog" src="com.example.ui.dialog" />
|
|
46
|
+
<Widget id="bottomSheet" src="com.example.ui.bottomsheet" />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Build the stable proxy tree in XML. JavaScript may update properties and supply
|
|
50
|
+
`ListView` data; it must not reconstruct the complete surface imperatively.
|
|
51
|
+
|
|
52
|
+
## Snackbar
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
$.snackbar.show({
|
|
56
|
+
message,
|
|
57
|
+
tone: 'neutral', // neutral | success | warning | error
|
|
58
|
+
duration: 4500,
|
|
59
|
+
actionTitle,
|
|
60
|
+
onAction,
|
|
61
|
+
onDismiss
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
$.snackbar.dismiss()
|
|
65
|
+
$.snackbar.destroy()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Contract:
|
|
69
|
+
|
|
70
|
+
- A new message replaces the visible message and restarts the timer.
|
|
71
|
+
- Default `duration` is 4500 ms; `duration: 0` remains visible until an action,
|
|
72
|
+
`dismiss()`, replacement, or destruction.
|
|
73
|
+
- Allow at most one text action, such as Undo.
|
|
74
|
+
- Invoke `onAction` once, then close the Snackbar.
|
|
75
|
+
- Invoke `onDismiss` once with the reason `timeout`, `action`, `replaced`,
|
|
76
|
+
`programmatic`, or `destroy`.
|
|
77
|
+
- Clear the old timer before replacement and on every close path.
|
|
78
|
+
- Position above bottom navigation and inside the current safe area.
|
|
79
|
+
- Queueing Snackbars is not the default. The newest relevant message wins.
|
|
80
|
+
|
|
81
|
+
## Dialog
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
$.dialog.attach(window)
|
|
85
|
+
|
|
86
|
+
$.dialog.show({
|
|
87
|
+
title,
|
|
88
|
+
message,
|
|
89
|
+
tone: 'neutral', // neutral | warning | error
|
|
90
|
+
confirmTitle,
|
|
91
|
+
cancelTitle,
|
|
92
|
+
destructive: false,
|
|
93
|
+
onConfirm,
|
|
94
|
+
onCancel
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
$.dialog.dismiss('programmatic')
|
|
98
|
+
$.dialog.destroy()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Contract:
|
|
102
|
+
|
|
103
|
+
- Without `cancelTitle`, present an acknowledgement. With it, present a
|
|
104
|
+
confirmation.
|
|
105
|
+
- Enqueue additional `show()` calls FIFO. Never stack two Dialogs visually.
|
|
106
|
+
- Disable both buttons on the first accepted tap.
|
|
107
|
+
- Invoke `onConfirm` or `onCancel` at most once; never both.
|
|
108
|
+
- Cancel button, Android Back, or allowed backdrop dismissal invokes `onCancel`.
|
|
109
|
+
Android Back closes an acknowledgement as its single dismissal action.
|
|
110
|
+
- A destructive confirmation must not close from a backdrop tap.
|
|
111
|
+
- `dismiss('programmatic')` closes without treating the operation as a user
|
|
112
|
+
confirmation or cancellation.
|
|
113
|
+
- `destroy()` clears the active request and queue without running business
|
|
114
|
+
callbacks.
|
|
115
|
+
- Use platform-familiar safe/cancel ordering and a distinct destructive role;
|
|
116
|
+
do not hard-code one platform's button order for both.
|
|
117
|
+
|
|
118
|
+
## Bottom Sheet
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
$.bottomSheet.attach(window)
|
|
122
|
+
|
|
123
|
+
$.bottomSheet.show({
|
|
124
|
+
title,
|
|
125
|
+
message,
|
|
126
|
+
items: [{
|
|
127
|
+
id,
|
|
128
|
+
title,
|
|
129
|
+
subtitle,
|
|
130
|
+
icon,
|
|
131
|
+
tone: 'neutral',
|
|
132
|
+
enabled: true
|
|
133
|
+
}],
|
|
134
|
+
selectedId,
|
|
135
|
+
onSelect,
|
|
136
|
+
onCancel
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
$.bottomSheet.dismiss('cancel')
|
|
140
|
+
$.bottomSheet.destroy()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Contract:
|
|
144
|
+
|
|
145
|
+
- Render items with a fixed `ListView` template and data objects. Support long
|
|
146
|
+
lists, disabled rows, optional subtitles/icons, and a selected marker.
|
|
147
|
+
- Enqueue additional `show()` calls FIFO. Never stack sheets.
|
|
148
|
+
- Accept the first enabled selection only, invoke `onSelect(item)` once, and
|
|
149
|
+
close.
|
|
150
|
+
- Backdrop, Android Back, Cancel, or `dismiss('cancel')` invokes `onCancel` once.
|
|
151
|
+
- Other programmatic dismissal and `destroy()` do not imply cancellation and do
|
|
152
|
+
not execute business callbacks.
|
|
153
|
+
- Present from the bottom on phones. On tablets and large screens, use a centered,
|
|
154
|
+
width-limited action surface. Reserve `anchor` for a future anchored variant;
|
|
155
|
+
do not expose behavior that is not implemented.
|
|
156
|
+
|
|
157
|
+
## Shared lifecycle contract
|
|
158
|
+
|
|
159
|
+
- Prefer one instance of each needed Widget per window. Add a global coordinator
|
|
160
|
+
only after real cross-window requirements exist.
|
|
161
|
+
- Call `attach(window)` after the host window/controller exists. Call `destroy()`
|
|
162
|
+
from the host's close cleanup.
|
|
163
|
+
- Make `destroy()` idempotent. Remove listeners, clear timers, cancel pending
|
|
164
|
+
animation callbacks, clear queues, and release callback references.
|
|
165
|
+
- Guard every asynchronous completion with the current presentation generation
|
|
166
|
+
so a stale timer or animation cannot dismiss a newer request.
|
|
167
|
+
- Consume backdrop events and prevent them from bubbling to underlying controls.
|
|
168
|
+
- Save the host's previous accessibility state, exclude underlying content while
|
|
169
|
+
modal, move focus to the surface, and restore the saved state on close.
|
|
170
|
+
- Keep localized copy outside the Widget. Pass strings and business callbacks in
|
|
171
|
+
the public API.
|
|
172
|
+
- Keep business logic out of the Widget. It reports a choice; the host performs
|
|
173
|
+
domain work.
|
|
174
|
+
- Verify every callback path for exactly-once behavior: action, confirm, cancel,
|
|
175
|
+
Back, backdrop, timeout, replacement, programmatic dismissal, and destruction.
|
|
176
|
+
|
|
177
|
+
## PurgeTSS integration
|
|
178
|
+
|
|
179
|
+
When `purgetss/` or `purgetss/config.cjs` exists, load the `purgetss` skill before
|
|
180
|
+
writing styles. `ti-expert` decides the component boundary; `purgetss` decides
|
|
181
|
+
the verified utilities and configuration.
|
|
182
|
+
|
|
183
|
+
Apply these architectural rules:
|
|
184
|
+
|
|
185
|
+
- Set PurgeTSS `options.widgets` to `true` so Widget XML is scanned.
|
|
186
|
+
- Express stable anatomy and utility classes in Widget XML.
|
|
187
|
+
- Use semantic color names for surfaces, text, borders, accent, overlay, success,
|
|
188
|
+
warning, and error. Let Titanium resolve light/dark variants.
|
|
189
|
+
- Do not edit generated `app/styles/app.tss`.
|
|
190
|
+
- Use `widget.tss` only for properties that have no verified utility or that are
|
|
191
|
+
inherently specific to the Widget.
|
|
192
|
+
- Declare animations as XML `Animation` proxies using `purgetss.ui`. Use explicit
|
|
193
|
+
`open()` and `close()` calls instead of toggle behavior.
|
|
194
|
+
- After generation, inspect the final `app.tss` section for unused or unsupported
|
|
195
|
+
classes and resolve every new entry.
|
|
196
|
+
|
|
197
|
+
Do not copy class inventories into this skill and do not guess Tailwind-like
|
|
198
|
+
names. The installed PurgeTSS skill remains the source of truth.
|
|
199
|
+
|
|
200
|
+
## Widget, Require, state, or native UI
|
|
201
|
+
|
|
202
|
+
| Choose | When |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| Alloy Widget | Self-contained public API, state/lifecycle ownership, portable boundary, or reuse across apps/screens |
|
|
205
|
+
| `<Require>` controller | App-specific view/controller composition that shares the host's domain and lifecycle |
|
|
206
|
+
| Screen state in XML | Loading, empty, content, or error presentation belongs only to that screen |
|
|
207
|
+
| TSS or PurgeTSS utilities | Repeated visual treatment has no behavior, state, or lifecycle |
|
|
208
|
+
| Native/system UI | The operating system owns permissions, trust, files, sharing, identity, purchases, or another protected flow |
|
|
209
|
+
|
|
210
|
+
Usage count is evidence, not a threshold. A component can deserve a Widget on
|
|
211
|
+
its first use if it has a stable portable API and non-trivial lifecycle; three
|
|
212
|
+
similar labels still do not deserve one.
|
|
213
|
+
|
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
# Migration patterns for legacy Titanium apps
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [Assessment checklist](#assessment-checklist)
|
|
7
|
+
- [Phase 1: styling-system organization](#phase-1-styling-system-organization)
|
|
8
|
+
- [Phase 1B: feedback surfaces](#phase-1b-feedback-surfaces)
|
|
9
|
+
- [Phase 2: service layer extraction](#phase-2-service-layer-extraction)
|
|
10
|
+
- [Phase 3: event system migration](#phase-3-event-system-migration)
|
|
11
|
+
- [Phase 4: navigation service](#phase-4-navigation-service)
|
|
12
|
+
- [Phase 5: state management](#phase-5-state-management)
|
|
13
|
+
- [Migration order](#migration-order)
|
|
14
|
+
- [Rollback strategy](#rollback-strategy)
|
|
15
|
+
- [Common migration pitfalls](#common-migration-pitfalls)
|
|
16
|
+
|
|
17
|
+
<!-- TOC-END -->
|
|
18
|
+
|
|
3
19
|
## Assessment checklist
|
|
4
20
|
|
|
5
21
|
Before migrating, evaluate the current codebase:
|
|
6
22
|
|
|
7
23
|
| Area | Signs of Legacy Code | Target State |
|
|
8
24
|
| ----------- | -------------------------------------------------- | --------------------------------------------- |
|
|
9
|
-
| Styling | Inline attributes, scattered styles |
|
|
25
|
+
| Styling | Inline attributes, scattered styles | PurgeTSS utilities when detected; otherwise organized Alloy TSS |
|
|
10
26
|
| Controllers | 200+ lines, API calls, business logic | Thin orchestrators (<100 lines) |
|
|
11
27
|
| Events | `Ti.App.fireEvent` everywhere | Backbone.Events or StateStore |
|
|
12
28
|
| Navigation | Direct `Alloy.createController().getView().open()` | Navigation service |
|
|
13
29
|
| Data | Scattered `Ti.App.Properties`, no collections | Centralized state + Collections |
|
|
14
30
|
|
|
15
|
-
## Phase 1:
|
|
31
|
+
## Phase 1: styling-system organization
|
|
32
|
+
|
|
33
|
+
**Goal**: Migrate from inline and scattered styling into the styling system the
|
|
34
|
+
project actually uses.
|
|
35
|
+
|
|
36
|
+
### Step 0: detect PurgeTSS
|
|
37
|
+
|
|
38
|
+
Check for `purgetss/` or `purgetss/config.cjs` before editing styles.
|
|
16
39
|
|
|
17
|
-
**
|
|
40
|
+
- **PurgeTSS detected:** invoke the `purgetss` skill, use verified utilities and
|
|
41
|
+
semantic colors, configure reusable rules in `config.cjs`, and never edit the
|
|
42
|
+
generated `app/styles/app.tss`.
|
|
43
|
+
- **No PurgeTSS:** use standard per-controller TSS and a manually maintained
|
|
44
|
+
`app/styles/app.tss` for genuinely global selectors.
|
|
45
|
+
|
|
46
|
+
Do not migrate a PurgeTSS application back into manual TSS by following a generic
|
|
47
|
+
Alloy example.
|
|
18
48
|
|
|
19
49
|
### Step 1: audit existing styles
|
|
20
50
|
|
|
21
|
-
Find
|
|
51
|
+
Find inline attributes and classify them for the detected styling system:
|
|
22
52
|
```bash
|
|
23
53
|
# Find inline styling in XML views
|
|
24
|
-
|
|
25
|
-
grep -r 'font=' app/views/
|
|
26
|
-
grep -r 'color=' app/views/
|
|
54
|
+
rg -n 'backgroundColor=|font=|color=' app/views/
|
|
27
55
|
```
|
|
28
56
|
|
|
29
57
|
### Step 2: move inline attributes to TSS
|
|
@@ -36,7 +64,7 @@ grep -r 'color=' app/views/
|
|
|
36
64
|
```
|
|
37
65
|
|
|
38
66
|
```xml
|
|
39
|
-
<!-- AFTER:
|
|
67
|
+
<!-- AFTER in standard Alloy: clean XML + TSS file -->
|
|
40
68
|
<View id="header">
|
|
41
69
|
<Label id="title" text="L('welcome')" />
|
|
42
70
|
</View>
|
|
@@ -48,9 +76,11 @@ grep -r 'color=' app/views/
|
|
|
48
76
|
"#title": { color: "#333", font: { fontSize: 18, fontWeight: "bold" } }
|
|
49
77
|
```
|
|
50
78
|
|
|
51
|
-
### Step 3: organize TSS
|
|
79
|
+
### Step 3: organize standard Alloy TSS
|
|
80
|
+
|
|
81
|
+
This step applies only when PurgeTSS is not present.
|
|
52
82
|
|
|
53
|
-
1. Use `app.tss` for global styles
|
|
83
|
+
1. Use `app.tss` for genuinely global styles shared across views
|
|
54
84
|
2. Use per-controller TSS files for view-specific styles
|
|
55
85
|
3. Use class selectors for reusable style patterns:
|
|
56
86
|
```tss
|
|
@@ -59,6 +89,18 @@ grep -r 'color=' app/views/
|
|
|
59
89
|
".btn-primary": { backgroundColor: '#2563eb', color: '#fff', height: 44, borderRadius: 8 }
|
|
60
90
|
```
|
|
61
91
|
|
|
92
|
+
In either styling path, replace hard-coded visual colors with semantic roles as
|
|
93
|
+
part of the migration. See [Theming and Dark Mode](theming.md).
|
|
94
|
+
|
|
95
|
+
## Phase 1B: feedback surfaces
|
|
96
|
+
|
|
97
|
+
Inventory native alerts, option dialogs, Toasts, and local overlays. Classify
|
|
98
|
+
each occurrence before replacing it, then migrate one window at a time. Preserve
|
|
99
|
+
system-owned pickers, permission prompts, sharing, and biometric UI.
|
|
100
|
+
|
|
101
|
+
Use [Feedback Migration](feedback-migration.md) for the sequence, deliberate
|
|
102
|
+
silence rules, and verification contract.
|
|
103
|
+
|
|
62
104
|
## Phase 2: service layer extraction
|
|
63
105
|
|
|
64
106
|
**Goal**: Move business logic from controllers to services.
|
|
@@ -265,11 +307,12 @@ const { user } = appStore.getState()
|
|
|
265
307
|
|
|
266
308
|
Recommended sequence to minimize risk:
|
|
267
309
|
|
|
268
|
-
1. **
|
|
269
|
-
2. **
|
|
270
|
-
3. **
|
|
271
|
-
4. **
|
|
272
|
-
5. **
|
|
310
|
+
1. **Styling-system organization** - Establish semantic tokens and the correct PurgeTSS/Alloy path
|
|
311
|
+
2. **Feedback surfaces** - Migrate one window without changing domain outcomes
|
|
312
|
+
3. **Service Layer** - Extract logic without changing behavior
|
|
313
|
+
4. **Navigation Service** - Centralize with cleanup
|
|
314
|
+
5. **Event System** - Replace Ti.App events
|
|
315
|
+
6. **State Management** - Last, as it touches everything
|
|
273
316
|
|
|
274
317
|
## Rollback strategy
|
|
275
318
|
|
|
@@ -278,16 +321,10 @@ For each phase:
|
|
|
278
321
|
1. **Create a branch** before starting
|
|
279
322
|
2. **Migrate one feature** completely
|
|
280
323
|
3. **Test thoroughly** before moving to next feature
|
|
281
|
-
4. **
|
|
324
|
+
4. **Delete the replaced implementation** after the vertical slice passes
|
|
282
325
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Legacy:
|
|
286
|
-
// Ti.App.fireEvent('user:updated', data)
|
|
287
|
-
|
|
288
|
-
// New:
|
|
289
|
-
Alloy.Events.trigger('user:updated', data)
|
|
290
|
-
```
|
|
326
|
+
Use version control for rollback. Do not keep commented legacy implementations:
|
|
327
|
+
they conceal duplicate listeners, stale callbacks, and incomplete migrations.
|
|
291
328
|
|
|
292
329
|
## Common migration pitfalls
|
|
293
330
|
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Architectural patterns for Titanium + Alloy
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [1. Native module abstraction (Wrapper)](#1-native-module-abstraction-wrapper)
|
|
7
|
+
- [2. Repository pattern for data](#2-repository-pattern-for-data)
|
|
8
|
+
- [3. Service layer pattern](#3-service-layer-pattern)
|
|
9
|
+
- [4. Event bus for decoupling](#4-event-bus-for-decoupling)
|
|
10
|
+
- [5. Factory pattern for dynamic views](#5-factory-pattern-for-dynamic-views)
|
|
11
|
+
- [6. Navigation service pattern](#6-navigation-service-pattern)
|
|
12
|
+
- [7. Collection binding pattern](#7-collection-binding-pattern)
|
|
13
|
+
- [8. Cleanup pattern](#8-cleanup-pattern)
|
|
14
|
+
- [9. Singleton service pattern](#9-singleton-service-pattern)
|
|
15
|
+
|
|
16
|
+
<!-- TOC-END -->
|
|
17
|
+
|
|
3
18
|
## 1. Native module abstraction (Wrapper)
|
|
4
19
|
|
|
5
20
|
**Use when:** Using native modules like Maps, Biometrics, or specialized Media players.
|
|
@@ -446,3 +461,4 @@ class AnalyticsService {
|
|
|
446
461
|
}
|
|
447
462
|
|
|
448
463
|
exports.Analytics = new AnalyticsService()
|
|
464
|
+
```
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Performance optimization: bridge, memory, animation & timing
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [Bridge optimization](#bridge-optimization)
|
|
7
|
+
- [Memory management](#memory-management)
|
|
8
|
+
- [Image memory management](#image-memory-management)
|
|
9
|
+
- [Lazy loading pattern](#lazy-loading-pattern)
|
|
10
|
+
- [Database performance](#database-performance)
|
|
11
|
+
- [Performance monitoring](#performance-monitoring)
|
|
12
|
+
- [Animation performance](#animation-performance)
|
|
13
|
+
- [Hardware-accelerated properties](#hardware-accelerated-properties)
|
|
14
|
+
- [Animation cleanup](#animation-cleanup)
|
|
15
|
+
- [Debouncing and throttling](#debouncing-and-throttling)
|
|
16
|
+
- [Performance checklist](#performance-checklist)
|
|
17
|
+
|
|
18
|
+
<!-- TOC-END -->
|
|
19
|
+
|
|
3
20
|
## Bridge optimization
|
|
4
21
|
|
|
5
22
|
### Minimize bridge crossings
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Device security: biometrics, deep links & integrity
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [Biometric authentication](#biometric-authentication)
|
|
7
|
+
- [Biometric login flow](#biometric-login-flow)
|
|
8
|
+
- [Deep link security](#deep-link-security)
|
|
9
|
+
- [Registering deep link handler](#registering-deep-link-handler)
|
|
10
|
+
- [Jailbreak/Root detection](#jailbreakroot-detection)
|
|
11
|
+
- [Integrating security checks](#integrating-security-checks)
|
|
12
|
+
- [Security checklist](#security-checklist)
|
|
13
|
+
|
|
14
|
+
<!-- TOC-END -->
|
|
15
|
+
|
|
3
16
|
## Biometric authentication
|
|
4
17
|
|
|
5
18
|
### Using ti.identity module
|
|
@@ -92,6 +105,9 @@ const { TokenStorage } = require('services/tokenStorage')
|
|
|
92
105
|
const { AuthService } = require('services/authService')
|
|
93
106
|
|
|
94
107
|
function init() {
|
|
108
|
+
// The login view declares the app-owned Dialog Widget as its last child.
|
|
109
|
+
$.dialog.attach($.getView())
|
|
110
|
+
|
|
95
111
|
// Check if biometric login is available and enabled
|
|
96
112
|
const { available, type } = BiometricService.checkAvailability()
|
|
97
113
|
const biometricEnabled = Ti.App.Properties.getBool('biometricEnabled', false)
|
|
@@ -130,22 +146,25 @@ async function onLoginSuccess(user) {
|
|
|
130
146
|
if (available && !Ti.App.Properties.getBool('askedBiometric', false)) {
|
|
131
147
|
Ti.App.Properties.setBool('askedBiometric', true)
|
|
132
148
|
|
|
133
|
-
//
|
|
134
|
-
|
|
149
|
+
// The choice is app-owned; the later biometric authentication prompt remains native.
|
|
150
|
+
$.dialog.show({
|
|
135
151
|
title: String.format(L('enable_biometric_title'), type),
|
|
136
152
|
message: String.format(L('enable_biometric_msg'), type),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
dialog.addEventListener('click', (e) => {
|
|
141
|
-
if (e.index === 1) {
|
|
153
|
+
cancelTitle: L('not_now'),
|
|
154
|
+
confirmTitle: L('enable'),
|
|
155
|
+
onConfirm: () => {
|
|
142
156
|
Ti.App.Properties.setBool('biometricEnabled', true)
|
|
143
157
|
}
|
|
144
158
|
})
|
|
145
|
-
|
|
146
|
-
dialog.show()
|
|
147
159
|
}
|
|
148
160
|
}
|
|
161
|
+
|
|
162
|
+
function cleanup() {
|
|
163
|
+
$.dialog.destroy()
|
|
164
|
+
$.destroy()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
$.cleanup = cleanup
|
|
149
168
|
```
|
|
150
169
|
|
|
151
170
|
## Deep link security
|
|
@@ -469,7 +488,9 @@ exports.SecurityService = {
|
|
|
469
488
|
enforceSecurityPolicy() {
|
|
470
489
|
const { compromised, reasons } = this.checkDeviceIntegrity()
|
|
471
490
|
|
|
472
|
-
if (!compromised)
|
|
491
|
+
if (!compromised) {
|
|
492
|
+
return { outcome: 'allow', reasons: [], restricted: false }
|
|
493
|
+
}
|
|
473
494
|
|
|
474
495
|
// Log security event
|
|
475
496
|
logger.error('Security', 'Compromised device detected', {
|
|
@@ -483,50 +504,16 @@ exports.SecurityService = {
|
|
|
483
504
|
|
|
484
505
|
switch (policy) {
|
|
485
506
|
case 'block':
|
|
486
|
-
|
|
487
|
-
this._showBlockedScreen()
|
|
488
|
-
return false
|
|
507
|
+
return { outcome: 'block', reasons, restricted: true }
|
|
489
508
|
|
|
490
509
|
case 'restrict':
|
|
491
|
-
// Disable sensitive features
|
|
492
510
|
Ti.App.Properties.setBool('restrictedMode', true)
|
|
493
|
-
|
|
494
|
-
return true
|
|
511
|
+
return { outcome: 'warn', reasons, restricted: true }
|
|
495
512
|
|
|
496
513
|
case 'warn':
|
|
497
514
|
default:
|
|
498
|
-
|
|
499
|
-
this._showWarning()
|
|
500
|
-
return true
|
|
515
|
+
return { outcome: 'warn', reasons, restricted: false }
|
|
501
516
|
}
|
|
502
|
-
},
|
|
503
|
-
|
|
504
|
-
_showBlockedScreen() {
|
|
505
|
-
// Note: iOS guidelines prohibit programmatically terminating an app.
|
|
506
|
-
// The best practice is to show a blocking dialog and prevent further interaction.
|
|
507
|
-
// On Android you can call Ti.Android.currentActivity.finish() to close the activity.
|
|
508
|
-
const dialog = Ti.UI.createAlertDialog({
|
|
509
|
-
title: L('security_blocked_title'),
|
|
510
|
-
message: L('security_blocked_msg'),
|
|
511
|
-
buttonNames: [L('close')]
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
dialog.addEventListener('click', () => {
|
|
515
|
-
if (OS_ANDROID) {
|
|
516
|
-
Ti.Android.currentActivity.finish()
|
|
517
|
-
}
|
|
518
|
-
// On iOS: leave the dialog visible — Apple guidelines prohibit force-quitting apps.
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
dialog.show()
|
|
522
|
-
},
|
|
523
|
-
|
|
524
|
-
_showWarning() {
|
|
525
|
-
Ti.UI.createAlertDialog({
|
|
526
|
-
title: L('security_warning_title'),
|
|
527
|
-
message: L('security_warning_msg'),
|
|
528
|
-
buttonNames: [L('i_understand')]
|
|
529
|
-
}).show()
|
|
530
517
|
}
|
|
531
518
|
}
|
|
532
519
|
```
|
|
@@ -537,18 +524,45 @@ exports.SecurityService = {
|
|
|
537
524
|
// alloy.js
|
|
538
525
|
const { SecurityService } = require('services/securityService')
|
|
539
526
|
|
|
540
|
-
//
|
|
541
|
-
const
|
|
527
|
+
// Services return policy; they do not create UI before the root exists.
|
|
528
|
+
const securityDecision = SecurityService.enforceSecurityPolicy()
|
|
529
|
+
const root = Alloy.createController('index', { securityDecision })
|
|
530
|
+
root.getView().open()
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```javascript
|
|
534
|
+
// controllers/index.js
|
|
535
|
+
const decision = $.args.securityDecision
|
|
536
|
+
|
|
537
|
+
function init() {
|
|
538
|
+
$.dialog.attach($.indexWindow)
|
|
539
|
+
|
|
540
|
+
if (decision.outcome === 'block') {
|
|
541
|
+
// Show a dedicated blocking state in the root XML and do not initialize
|
|
542
|
+
// protected application content. Do not force-quit the app.
|
|
543
|
+
$.blockingState.visible = true
|
|
544
|
+
$.appContent.visible = false
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
initializeAppContent({ restricted: decision.restricted })
|
|
549
|
+
|
|
550
|
+
if (decision.outcome === 'warn') {
|
|
551
|
+
$.dialog.show({
|
|
552
|
+
title: L('security_warning_title'),
|
|
553
|
+
message: L('security_warning_msg'),
|
|
554
|
+
tone: 'warning',
|
|
555
|
+
confirmTitle: L('i_understand')
|
|
556
|
+
})
|
|
557
|
+
}
|
|
558
|
+
}
|
|
542
559
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
560
|
+
function cleanup() {
|
|
561
|
+
$.dialog.destroy()
|
|
562
|
+
$.destroy()
|
|
546
563
|
}
|
|
547
564
|
|
|
548
|
-
|
|
549
|
-
setInterval(() => {
|
|
550
|
-
SecurityService.checkDeviceIntegrity()
|
|
551
|
-
}, 5 * 60 * 1000) // Every 5 minutes
|
|
565
|
+
$.cleanup = cleanup
|
|
552
566
|
```
|
|
553
567
|
|
|
554
568
|
## Security checklist
|
|
@@ -565,3 +579,4 @@ setInterval(() => {
|
|
|
565
579
|
| **Integrity** | Check for jailbreak/root | checkDeviceIntegrity() |
|
|
566
580
|
| **Integrity** | Define security policy | block/restrict/warn |
|
|
567
581
|
| **Integrity** | Log security events | Always log compromises |
|
|
582
|
+
| **Integrity UI** | Keep policy out of services | Root controller presents blocking state or Dialog |
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# State management patterns
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [The problem](#the-problem)
|
|
7
|
+
- [Solution: centralized state store](#solution-centralized-state-store)
|
|
8
|
+
- [State store usage](#state-store-usage)
|
|
9
|
+
- [Service layer state caching](#service-layer-state-caching)
|
|
10
|
+
- [Collections vs state store](#collections-vs-state-store)
|
|
11
|
+
- [State synchronization pattern](#state-synchronization-pattern)
|
|
12
|
+
- [Anti-patterns](#anti-patterns)
|
|
13
|
+
- [Persistence strategies](#persistence-strategies)
|
|
14
|
+
- [State middleware](#state-middleware)
|
|
15
|
+
- [State debugging](#state-debugging)
|
|
16
|
+
|
|
17
|
+
<!-- TOC-END -->
|
|
18
|
+
|
|
3
19
|
## The problem
|
|
4
20
|
|
|
5
21
|
As apps grow, state gets scattered:
|
|
@@ -730,6 +746,7 @@ appStore.use(debugMiddleware)
|
|
|
730
746
|
// controllers/debug/stateViewer.js (Development only)
|
|
731
747
|
const { StateDebug } = require('services/stateDebug')
|
|
732
748
|
const { appStore } = require('services/stateStore')
|
|
749
|
+
const { Navigation } = require('services/navigation')
|
|
733
750
|
|
|
734
751
|
function init() {
|
|
735
752
|
if (!Alloy.CFG.debug) {
|
|
@@ -763,12 +780,11 @@ function onHistoryItemClick(e) {
|
|
|
763
780
|
const index = e.itemIndex
|
|
764
781
|
const entry = StateDebug.getHistory()[index]
|
|
765
782
|
|
|
766
|
-
//
|
|
767
|
-
|
|
783
|
+
// Structured, potentially long content belongs in a scrollable debug screen.
|
|
784
|
+
Navigation.open('debug/stateDiff', {
|
|
768
785
|
title: entry.action,
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}).show()
|
|
786
|
+
content: JSON.stringify(entry.diff, null, 2)
|
|
787
|
+
})
|
|
772
788
|
}
|
|
773
789
|
|
|
774
790
|
function cleanup() {
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# End-to-end testing & CI/CD
|
|
2
2
|
|
|
3
|
+
<!-- TOC-START -->
|
|
4
|
+
## Contents
|
|
5
|
+
|
|
6
|
+
- [Appium setup](#appium-setup)
|
|
7
|
+
- [Appium configuration](#appium-configuration)
|
|
8
|
+
- [Webdriverio integration](#webdriverio-integration)
|
|
9
|
+
- [E2E test example](#e2e-test-example)
|
|
10
|
+
- [Adding accessibility IDs for testing](#adding-accessibility-ids-for-testing)
|
|
11
|
+
- [Page object pattern](#page-object-pattern)
|
|
12
|
+
- [CI/CD integration](#cicd-integration)
|
|
13
|
+
- [Fastlane integration](#fastlane-integration)
|
|
14
|
+
- [Package.json scripts](#packagejson-scripts)
|
|
15
|
+
- [Branch protection rules](#branch-protection-rules)
|
|
16
|
+
- [Testing best practices summary](#testing-best-practices-summary)
|
|
17
|
+
|
|
18
|
+
<!-- TOC-END -->
|
|
19
|
+
|
|
3
20
|
## Appium setup
|
|
4
21
|
|
|
5
22
|
```bash
|