@maccesar/titools 2.2.12 → 2.3.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 (98) hide show
  1. package/README.md +255 -713
  2. package/bin/titools.js +1 -1
  3. package/lib/commands/update.js +5 -6
  4. package/lib/config.js +1 -0
  5. package/lib/downloader.js +10 -0
  6. package/package.json +1 -1
  7. package/skills/alloy-guides/SKILL.md +46 -48
  8. package/skills/alloy-guides/references/CONTROLLERS.md +4 -6
  9. package/skills/alloy-guides/references/MODELS.md +340 -184
  10. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +3 -4
  11. package/skills/alloy-guides/references/VIEWS_STYLES.md +44 -46
  12. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +4 -5
  13. package/skills/alloy-guides/references/VIEWS_XML.md +51 -17
  14. package/skills/alloy-guides/references/WIDGETS.md +1 -1
  15. package/skills/alloy-howtos/SKILL.md +12 -13
  16. package/skills/alloy-howtos/references/cli_reference.md +40 -41
  17. package/skills/alloy-howtos/references/samples.md +3 -4
  18. package/skills/purgetss/SKILL.md +369 -356
  19. package/skills/purgetss/references/EXAMPLES.md +24 -25
  20. package/skills/purgetss/references/animation-advanced.md +555 -0
  21. package/skills/purgetss/references/animation-system.md +395 -995
  22. package/skills/purgetss/references/apply-directive.md +111 -141
  23. package/skills/purgetss/references/arbitrary-values.md +206 -480
  24. package/skills/purgetss/references/class-categories.md +277 -0
  25. package/skills/purgetss/references/class-index.md +1 -420
  26. package/skills/purgetss/references/cli-commands.md +446 -556
  27. package/skills/purgetss/references/configurable-properties.md +163 -599
  28. package/skills/purgetss/references/custom-rules.md +33 -76
  29. package/skills/purgetss/references/customization-deep-dive.md +319 -518
  30. package/skills/purgetss/references/dynamic-component-creation.md +33 -37
  31. package/skills/purgetss/references/grid-layout.md +42 -371
  32. package/skills/purgetss/references/icon-fonts.md +82 -475
  33. package/skills/purgetss/references/installation-setup.md +159 -331
  34. package/skills/purgetss/references/migration-guide.md +54 -109
  35. package/skills/purgetss/references/opacity-modifier.md +25 -222
  36. package/skills/purgetss/references/performance-tips.md +2 -2
  37. package/skills/purgetss/references/platform-modifiers.md +21 -407
  38. package/skills/purgetss/references/tikit-components.md +237 -104
  39. package/skills/purgetss/references/titanium-resets.md +20 -21
  40. package/skills/purgetss/references/ui-ux-design.md +171 -1702
  41. package/skills/ti-api/SKILL.md +109 -0
  42. package/skills/ti-api/references/api-android.md +675 -0
  43. package/skills/ti-api/references/api-app-platform.md +636 -0
  44. package/skills/ti-api/references/api-core.md +764 -0
  45. package/skills/ti-api/references/api-data-network.md +641 -0
  46. package/skills/ti-api/references/api-media.md +655 -0
  47. package/skills/ti-api/references/api-modules-ble-bluetooth.md +657 -0
  48. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +411 -0
  49. package/skills/ti-api/references/api-modules-map.md +632 -0
  50. package/skills/ti-api/references/api-modules-nfc.md +725 -0
  51. package/skills/ti-api/references/api-modules-social-misc.md +526 -0
  52. package/skills/ti-api/references/api-services.md +700 -0
  53. package/skills/ti-api/references/api-ui-android.md +499 -0
  54. package/skills/ti-api/references/api-ui-extras.md +702 -0
  55. package/skills/ti-api/references/api-ui-ios-animator.md +378 -0
  56. package/skills/ti-api/references/api-ui-ios.md +756 -0
  57. package/skills/ti-api/references/api-ui-lists.md +581 -0
  58. package/skills/ti-api/references/api-ui-text-input.md +607 -0
  59. package/skills/ti-api/references/api-ui-views.md +572 -0
  60. package/skills/ti-api/references/api-ui-windows-navigation.md +676 -0
  61. package/skills/ti-api/references/api-xml-global.md +743 -0
  62. package/skills/ti-expert/SKILL.md +46 -45
  63. package/skills/ti-expert/references/adaptive-layouts.md +414 -0
  64. package/skills/ti-expert/references/alloy-builtins.md +16 -20
  65. package/skills/ti-expert/references/alloy-structure.md +40 -42
  66. package/skills/ti-expert/references/anti-patterns.md +34 -0
  67. package/skills/ti-expert/references/code-conventions.md +5 -3
  68. package/skills/ti-expert/references/error-handling.md +14 -7
  69. package/skills/ti-expert/references/examples.md +4 -2
  70. package/skills/ti-expert/references/performance-optimization.md +26 -24
  71. package/skills/ti-expert/references/security-device.md +17 -14
  72. package/skills/ti-expert/references/security-fundamentals.md +60 -101
  73. package/skills/ti-expert/references/state-management.md +15 -14
  74. package/skills/ti-expert/references/testing-e2e-ci.md +30 -21
  75. package/skills/ti-expert/references/theming.md +12 -20
  76. package/skills/ti-guides/SKILL.md +13 -17
  77. package/skills/ti-guides/references/advanced-data-and-images.md +30 -10
  78. package/skills/ti-guides/references/application-frameworks.md +3 -3
  79. package/skills/ti-guides/references/coding-best-practices.md +31 -2
  80. package/skills/ti-guides/references/commonjs-advanced.md +46 -4
  81. package/skills/ti-guides/references/hello-world.md +9 -13
  82. package/skills/ti-guides/references/hyperloop-native-access.md +4 -1
  83. package/skills/ti-guides/references/javascript-primer.md +13 -5
  84. package/skills/ti-guides/references/resources.md +0 -2
  85. package/skills/ti-guides/references/style-and-conventions.md +1 -0
  86. package/skills/ti-guides/references/tiapp-config.md +0 -32
  87. package/skills/ti-howtos/SKILL.md +43 -45
  88. package/skills/ti-howtos/references/buffer-codec-streams.md +25 -3
  89. package/skills/ti-howtos/references/debugging-profiling.md +14 -25
  90. package/skills/ti-howtos/references/google-maps-v2.md +3 -3
  91. package/skills/ti-howtos/references/ios-map-kit.md +8 -1
  92. package/skills/ti-howtos/references/notification-services.md +0 -1
  93. package/skills/ti-howtos/references/webpack-build-pipeline.md +3 -0
  94. package/skills/ti-ui/SKILL.md +47 -49
  95. package/skills/ti-ui/references/application-structures.md +3 -2
  96. package/skills/ti-ui/references/orientation.md +17 -9
  97. package/skills/ti-ui/references/platform-ui-ios.md +63 -0
  98. package/skills/ti-ui/references/scrolling-views.md +39 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ti-expert
3
- description: "Titanium SDK architecture and implementation expert. Use when designing, reviewing, analyzing, or examining Titanium project structure (Alloy or Classic), creating controllers/views/services, choosing models vs collections, implementing communication patterns, handling memory cleanup, testing, auditing code, or migrating legacy apps. Automatically identifies project type."
3
+ description: "Titanium SDK architecture and implementation expert. Use when designing, reviewing, analyzing, or examining Titanium project structure (Alloy or Classic), creating controllers/views/services, choosing models vs collections, implementing communication patterns, handling memory cleanup, testing, auditing code, migrating legacy apps, or building adaptive/responsive layouts for tablets, foldables, and large screens. Automatically identifies project type."
4
4
  argument-hint: "[architecture-topic]"
5
5
  allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git *), Bash(node *)
6
6
  ---
@@ -11,23 +11,22 @@ Practical architecture and implementation guidance for Titanium SDK apps (Alloy
11
11
 
12
12
  ## Project detection
13
13
 
14
- :::info Auto-detects Alloy vs Classic projects
15
- This skill detects project type automatically and tailors guidance.
16
-
17
- Alloy indicators:
18
- - `app/` folder (MVC structure)
19
- - `app/views/`, `app/controllers/` folders
20
- - `alloy.jmk` or `config.json` files
21
-
22
- Classic indicators:
23
- - `Resources/` folder with `app.js` at root
24
- - No `app/` folder structure
25
-
26
- Behavior:
27
- - Alloy detected: provides Alloy MVC patterns and Backbone.js guidance
28
- - Classic detected: avoids Alloy-only patterns and recommends Classic options or migration
29
- - Unknown: asks the user to clarify the project type
30
- :::
14
+ > **️ℹ️ Auto-detects Alloy vs Classic projects**
15
+ > This skill detects project type automatically and tailors guidance.
16
+ >
17
+ > Alloy indicators:
18
+ > - `app/` folder (MVC structure)
19
+ > - `app/views/`, `app/controllers/` folders
20
+ > - `alloy.jmk` or `config.json` files
21
+ >
22
+ > Classic indicators:
23
+ > - `Resources/` folder with `app.js` at root
24
+ > - No `app/` folder structure
25
+ >
26
+ > Behavior:
27
+ > - Alloy detected: provides Alloy MVC patterns and Backbone.js guidance
28
+ > - Classic detected: avoids Alloy-only patterns and recommends Classic options or migration
29
+ > - Unknown: asks the user to clarify the project type
31
30
 
32
31
  ## Workflow
33
32
 
@@ -76,6 +75,8 @@ Detailed examples and full implementation samples are available in: [Architectur
76
75
 
77
76
  - No semicolons: let ASI handle it
78
77
  - Modern syntax: `const/let`, destructuring, template literals
78
+ - Prefer stable property ordering in JS objects and `applyProperties()` calls
79
+ - Default to alphabetical property order when it does not break a meaningful semantic grouping
79
80
  - `applyProperties()`: batch UI updates to reduce bridge crossings
80
81
  - Memory cleanup: any controller with global listeners must set `$.cleanup = cleanup`
81
82
  - Error handling: use AppError classes, log with context, never swallow errors
@@ -83,33 +84,32 @@ Detailed examples and full implementation samples are available in: [Architectur
83
84
 
84
85
  ## Titanium style sheets rules (low freedom)
85
86
 
86
- :::danger Critical: platform-specific properties require modifiers
87
- Using `Ti.UI.iOS.*` or `Ti.UI.Android.*` properties without platform modifiers breaks cross-platform builds.
88
-
89
- Example of the damage:
90
- ```tss
91
- // Wrong: adds Ti.UI.iOS to Android project
92
- "#mainWindow": {
93
- statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
94
- }
95
- ```
96
-
97
- Correct: always use platform modifiers
98
- ```tss
99
- // Correct: only adds to iOS
100
- "#mainWindow[platform=ios]": {
101
- statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
102
- }
103
- ```
104
-
105
- Properties that always need platform modifiers:
106
- - iOS: `statusBarStyle`, `modalStyle`, `modalTransitionStyle`, any `Ti.UI.iOS.*`
107
- - Android: `actionBar` configuration, any `Ti.UI.Android.*` constant
108
-
109
- Available modifiers: `[platform=ios]`, `[platform=android]`, `[formFactor=handheld]`, `[formFactor=tablet]`, `[if=Alloy.Globals.customVar]`
110
-
111
- For more platform-specific patterns, see the `ti-ui` skill.
112
- :::
87
+ > **🚨 Critical: platform-specific properties require modifiers**
88
+ > Using `Ti.UI.iOS.*` or `Ti.UI.Android.*` properties without platform modifiers breaks cross-platform builds.
89
+ >
90
+ > Example of the damage:
91
+ > ```tss
92
+ > // Wrong: adds Ti.UI.iOS to Android project
93
+ > "#mainWindow": {
94
+ > statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
95
+ > }
96
+ > ```
97
+ >
98
+ > Correct: always use platform modifiers
99
+ > ```tss
100
+ > // Correct: only adds to iOS
101
+ > "#mainWindow[platform=ios]": {
102
+ > statusBarStyle: Ti.UI.iOS.StatusBar.LIGHT_CONTENT
103
+ > }
104
+ > ```
105
+ >
106
+ > Properties that always need platform modifiers:
107
+ > - iOS: `statusBarStyle`, `modalStyle`, `modalTransitionStyle`, any `Ti.UI.iOS.*`
108
+ > - Android: `actionBar` configuration, any `Ti.UI.Android.*` constant
109
+ >
110
+ > Available modifiers: `[platform=ios]`, `[platform=android]`, `[formFactor=handheld]`, `[formFactor=tablet]`, `[if=Alloy.Globals.customVar]`
111
+ >
112
+ > For more platform-specific patterns, see the `ti-ui` skill.
113
113
 
114
114
  Titanium layout system:
115
115
  - Three layout modes: `layout: 'horizontal'`, `layout: 'vertical'`, and composite (default, no `layout` needed)
@@ -149,6 +149,7 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
149
149
  - [Contracts & Communication](references/contracts.md)
150
150
  - [State Management](references/state-management.md)
151
151
  - [Anti-patterns to Avoid](references/anti-patterns.md)
152
+ - [Adaptive Layouts](references/adaptive-layouts.md) (responsive breakpoints, tablet/foldable/desktop support, Android 17 resizability compliance)
152
153
 
153
154
  ### Implementation & API
154
155
  - [Alloy Builtins & Globals](references/alloy-builtins.md)
@@ -0,0 +1,414 @@
1
+ # Adaptive layouts
2
+
3
+ Patterns for building responsive Titanium apps that work across phones, tablets, foldables, and desktop mode.
4
+
5
+ This reference is NOT derived from official Titanium documentation. It addresses Google's Android 17 resizability requirements and provides reusable patterns for adaptive UI in Titanium/Alloy apps.
6
+
7
+ ## 1. Context: Android 17 resizability requirements
8
+
9
+ Starting with Android 17 (API 37), on devices with screens >= 600dp (tablets, foldables, desktop mode), Google ignores these manifest attributes:
10
+
11
+ - `android:screenOrientation` (all values, including `portrait`)
12
+ - `setRequestedOrientation()`
13
+ - `android:resizeableActivity`
14
+ - `android:minAspectRatio` / `android:maxAspectRatio`
15
+
16
+ Deadline: August 2027 — Google Play requires `targetSdkVersion="37"` for all new apps and updates.
17
+
18
+ Scope: Only affects screens >= 600dp. Phones (< 600dp) and games (`android:appCategory="game"`) are exempt.
19
+
20
+ What this means: portrait-only apps will be forced into landscape/resized on large screens. If the UI doesn't support it, it will break.
21
+
22
+ ### Related: edge-to-edge (API 36)
23
+
24
+ Before the resizability requirement (API 37), Android SDK 36 removes the opt-out for edge-to-edge rendering, causing Titanium layouts to overlap system navigation bars. The Titanium core team has a PR to handle this at the SDK level: tidev/titanium-sdk#14399 — adds an `EdgeToEdgeHelper` with correct padding so existing apps work without code changes.
25
+
26
+ | Android requirement | API level | Who handles it |
27
+ |---|---|---|
28
+ | Edge-to-edge (system bars) | SDK 36 | Titanium SDK (PR #14399) |
29
+ | Resizability / orientation unlock | SDK 37 | App developer (this reference) |
30
+
31
+ ### Full requirements checklist
32
+
33
+ | Requirement | Ti.UI.FILL/SIZE solves it? | What to do in Titanium |
34
+ |---|---|---|
35
+ | No orientation lock on >= 600dp | Yes | Remove the lock from tiapp.xml |
36
+ | Multi-window / split-screen | Yes | Avoid fixed dimensions |
37
+ | Variable aspect ratios | Yes | Use FILL/SIZE/percentages |
38
+ | Preserve state on config changes | **Handled by Titanium** | Titanium declares `android:configChanges` on all Activities automatically — Android does NOT destroy/recreate them on rotation, resize, or split-screen. No extra code needed. |
39
+ | External input (keyboard, mouse) | **No** | Handle `keypressed` events, ensure touch targets work with mouse clicks |
40
+
41
+ For most apps, removing the portrait lock and using `Ti.UI.FILL`/`Ti.UI.SIZE` correctly is sufficient. External input is only needed for apps targeting desktop mode or connected displays.
42
+
43
+ ### Two levels of adaptive layout
44
+
45
+ **Level 1 — Fluid layout (most apps):** Use `Ti.UI.FILL`, `Ti.UI.SIZE`, and percentages. No JS code needed. The layout engine adapts automatically. With PurgeTSS: `w-screen`, `h-auto`, and percentage utilities.
46
+
47
+ **Level 2 — Breakpoint-driven layout (structural changes):** Use the responsive helper + `postlayout` event + layout adapters when the structure must change (e.g., sidebar lateral on tablet, hidden on phone). The rest of this reference covers Level 2 patterns.
48
+
49
+ ## 2. Detecting window size
50
+
51
+ ### platformWidth vs win.size
52
+
53
+ There are two ways to get dimensions, and they behave differently:
54
+
55
+ | API | Returns | In split-screen |
56
+ |---|---|---|
57
+ | `Ti.Platform.displayCaps.platformWidth` | Full device screen width (pixels on Android) | Always returns full device width — WRONG for layout decisions |
58
+ | `$.win.size.width` (after `postlayout`) | Actual window width in app's default unit | Returns the real available width — CORRECT for layout decisions |
59
+
60
+ Google explicitly recommends: "do not use device screen size for layout decisions — use the actual window size." In native Android this is `WindowManager.getCurrentWindowMetrics()`. In Titanium, the equivalent is `win.size` after `postlayout`.
61
+
62
+ ### Units: dp is the default in Titanium
63
+
64
+ Titanium uses `dp` as the default unit. The `win.size` property returns values in the app's default unit. Since the default is dp, `win.size.width` already returns dp values — do NOT divide by `logicalDensityFactor` or you will get a double-conversion (halved values).
65
+
66
+ `platformWidth` is different: it returns absolute pixels on Android regardless of `ti.ui.defaultunit`. This requires dividing by `logicalDensityFactor` to convert to dp.
67
+
68
+ ### Responsive helper — `app/lib/responsive.js`
69
+
70
+ ```javascript
71
+ /**
72
+ * Detects breakpoints using the actual window size.
73
+ * Accurate in split-screen, multi-window, and freeform modes.
74
+ *
75
+ * win.size returns dp values (Titanium default unit is dp).
76
+ * Do NOT divide by logicalDensityFactor — that would double-convert.
77
+ */
78
+ const getInfoFromWindow = (winSize) => {
79
+ const dpWidth = winSize.width
80
+ const dpHeight = winSize.height
81
+
82
+ const isPortrait = dpHeight > dpWidth
83
+ const isTablet = Math.min(dpWidth, dpHeight) >= 600
84
+
85
+ let breakpoint = 'phone'
86
+ if (dpWidth >= 900) {
87
+ breakpoint = 'desktop'
88
+ } else if (dpWidth >= 600) {
89
+ breakpoint = 'tablet'
90
+ }
91
+
92
+ return { dpWidth, dpHeight, isPortrait, isTablet, breakpoint }
93
+ }
94
+
95
+ /**
96
+ * Fallback: detects breakpoints using device screen size.
97
+ * Use only when win.size is not available (e.g., before postlayout).
98
+ * NOT accurate in split-screen — returns full device width.
99
+ *
100
+ * platformWidth returns pixels on Android — must convert to dp.
101
+ */
102
+ const getInfoFromDevice = () => {
103
+ const { platformWidth: w, platformHeight: h, logicalDensityFactor: density } = Ti.Platform.displayCaps
104
+
105
+ const isPortrait = Ti.Gesture.portrait
106
+ const factor = OS_ANDROID ? density : 1
107
+ const dpWidth = (isPortrait ? Math.min(w, h) : Math.max(w, h)) / factor
108
+ const dpHeight = (isPortrait ? Math.max(w, h) : Math.min(w, h)) / factor
109
+ const isTablet = Math.min(w, h) / factor >= 600
110
+
111
+ let breakpoint = 'phone'
112
+ if (dpWidth >= 900) {
113
+ breakpoint = 'desktop'
114
+ } else if (dpWidth >= 600) {
115
+ breakpoint = 'tablet'
116
+ }
117
+
118
+ return { dpWidth, dpHeight, isPortrait, isTablet, breakpoint }
119
+ }
120
+
121
+ const onChange = (callback) => {
122
+ const handler = () => {
123
+ callback(getInfoFromDevice())
124
+ }
125
+
126
+ Ti.Gesture.addEventListener('orientationchange', handler)
127
+ return handler
128
+ }
129
+
130
+ const removeOff = (handler) => {
131
+ Ti.Gesture.removeEventListener('orientationchange', handler)
132
+ }
133
+
134
+ module.exports = { getInfoFromWindow, getInfoFromDevice, onChange, removeOff }
135
+ ```
136
+
137
+ ### Quick inline detection
138
+
139
+ ```javascript
140
+ // After postlayout — accurate in split-screen
141
+ const dpWidth = $.win.size.width // already dp, no conversion needed
142
+ const isTablet = Math.min($.win.size.width, $.win.size.height) >= 600
143
+ ```
144
+
145
+ ## 3. Layout strategy
146
+
147
+ | Breakpoint | dpWidth | Strategy |
148
+ | ---------- | --------- | ------------------------------------- |
149
+ | Phone | < 600dp | Vertical stack, sidebar hidden |
150
+ | Tablet | 600-899dp | Side-by-side split (30/70 or 40/60) |
151
+ | Desktop | >= 900dp | Fixed or percentage sidebar + fluid content |
152
+
153
+ ## 4. Controller pattern
154
+
155
+ ### View — `app/views/example.xml`
156
+
157
+ ```xml
158
+ <Alloy>
159
+ <Window id="win" backgroundColor="#fff">
160
+ <View id="container">
161
+ <View id="sidebar" />
162
+ <View id="content" />
163
+ </View>
164
+ </Window>
165
+ </Alloy>
166
+ ```
167
+
168
+ ### Styles — `app/styles/example.tss`
169
+
170
+ ```tss
171
+ "#container": {
172
+ width: Ti.UI.FILL,
173
+ height: Ti.UI.FILL
174
+ }
175
+
176
+ "#sidebar": {
177
+ backgroundColor: "#eee",
178
+ layout: "vertical"
179
+ }
180
+
181
+ "#content": {
182
+ backgroundColor: "#fff",
183
+ layout: "vertical"
184
+ }
185
+ ```
186
+
187
+ ### Controller — `app/controllers/example.js`
188
+
189
+ ```javascript
190
+ const Responsive = require('/responsive')
191
+
192
+ let state = Responsive.getInfoFromDevice()
193
+ let postlayoutHandler
194
+
195
+ // --- Layout adapters ---
196
+
197
+ function layoutPhone() {
198
+ $.container.layout = 'vertical'
199
+
200
+ $.sidebar.visible = false
201
+
202
+ $.content.applyProperties({
203
+ height: Ti.UI.FILL,
204
+ width: Ti.UI.FILL
205
+ })
206
+ }
207
+
208
+ function layoutTablet() {
209
+ $.container.layout = 'horizontal'
210
+
211
+ $.sidebar.applyProperties({
212
+ height: Ti.UI.FILL,
213
+ visible: true,
214
+ width: '30%'
215
+ })
216
+
217
+ $.content.applyProperties({
218
+ height: Ti.UI.FILL,
219
+ width: '70%'
220
+ })
221
+ }
222
+
223
+ function layoutDesktop() {
224
+ $.container.layout = 'horizontal'
225
+
226
+ $.sidebar.applyProperties({
227
+ height: Ti.UI.FILL,
228
+ visible: true,
229
+ width: 280
230
+ })
231
+
232
+ $.content.applyProperties({
233
+ height: Ti.UI.FILL,
234
+ width: Ti.UI.FILL
235
+ })
236
+ }
237
+
238
+ // --- Apply layout based on breakpoint ---
239
+
240
+ function applyLayout() {
241
+ switch (state.breakpoint) {
242
+ case 'phone':
243
+ layoutPhone()
244
+ break
245
+ case 'tablet':
246
+ layoutTablet()
247
+ break
248
+ case 'desktop':
249
+ layoutDesktop()
250
+ break
251
+ }
252
+ }
253
+
254
+ // --- Lifecycle ---
255
+
256
+ // Use postlayout to detect actual window size.
257
+ // This is accurate in split-screen where platformWidth is not.
258
+ postlayoutHandler = () => {
259
+ const newState = Responsive.getInfoFromWindow($.win.size)
260
+ const changed = newState.breakpoint !== state.breakpoint
261
+
262
+ state = newState
263
+
264
+ // Only re-apply layout when breakpoint changes.
265
+ // Percentage-based widths adapt automatically via the layout engine.
266
+ // Re-applying the same layout causes visible double-adjustment flicker.
267
+ if (changed) {
268
+ applyLayout()
269
+ }
270
+ }
271
+
272
+ $.win.addEventListener('postlayout', postlayoutHandler)
273
+
274
+ applyLayout()
275
+
276
+ // --- Memory cleanup (mandatory for controllers with global listeners) ---
277
+
278
+ function cleanup() {
279
+ if (postlayoutHandler) {
280
+ $.win.removeEventListener('postlayout', postlayoutHandler)
281
+ postlayoutHandler = null
282
+ }
283
+ }
284
+
285
+ $.win.addEventListener('close', cleanup)
286
+ $.cleanup = cleanup
287
+ ```
288
+
289
+ ## 5. Grid simulation
290
+
291
+ Titanium does NOT have a `gap` property. Use `left` / `top` offsets in horizontal/vertical layouts to simulate spacing between siblings.
292
+
293
+ ```javascript
294
+ function createRow() {
295
+ return Ti.UI.createView({
296
+ layout: 'horizontal',
297
+ height: Ti.UI.SIZE,
298
+ width: Ti.UI.FILL
299
+ })
300
+ }
301
+
302
+ function createCol(percent, spacing) {
303
+ return Ti.UI.createView({
304
+ width: percent + '%',
305
+ height: Ti.UI.SIZE,
306
+ left: spacing || 0
307
+ })
308
+ }
309
+
310
+ // Usage in a tablet layout:
311
+ function layoutTabletGrid() {
312
+ $.content.removeAllChildren()
313
+
314
+ const row = createRow()
315
+
316
+ const col1 = createCol(48)
317
+ col1.add(Ti.UI.createLabel({ text: 'Left column' }))
318
+
319
+ // 4% gap simulated via left offset
320
+ const col2 = createCol(48, '4%')
321
+ col2.add(Ti.UI.createLabel({ text: 'Right column' }))
322
+
323
+ row.add(col1)
324
+ row.add(col2)
325
+
326
+ $.content.add(row)
327
+ }
328
+ ```
329
+
330
+ ## 6. Anti-patterns
331
+
332
+ ```javascript
333
+ // ❌ Using platformWidth for layout decisions in split-screen
334
+ // Returns full device width, not the app's window width
335
+ const dpWidth = Ti.Platform.displayCaps.platformWidth / density
336
+ // Use $.win.size.width after postlayout instead
337
+
338
+ // ❌ Dividing win.size by logicalDensityFactor
339
+ // win.size already returns dp (Titanium's default unit) — double-conversion!
340
+ const dpWidth = $.win.size.width / density // WRONG: halves the value
341
+
342
+ // ❌ Using Ti.Gesture.isPortrait() — not a method, it's a property
343
+ Ti.Gesture.isPortrait() // TypeError!
344
+ Ti.Gesture.portrait // correct — boolean property
345
+
346
+ // ❌ Using gap property — does NOT exist in Titanium
347
+ { layout: 'horizontal', gap: 10 }
348
+
349
+ // ❌ Anonymous listeners on global objects — memory leak, can never be removed
350
+ Ti.Gesture.addEventListener('orientationchange', () => { ... })
351
+
352
+ // ❌ Re-applying layout when breakpoint hasn't changed — causes flicker
353
+ // Percentage-based widths already adapt via the layout engine
354
+ ```
355
+
356
+ ## 7. tiapp.xml configuration
357
+
358
+ ### Android: enable multiple orientations
359
+
360
+ The `<activity>` element must include `android:name` and be nested inside `<application>`. Without `android:name`, Android's manifest merger fails with "Missing 'name' key attribute on element activity".
361
+
362
+ Titanium generates the main activity name as `<app-id>.<Appname>Activity`. For example, app ID `com.example.myapp` with name `MyApp` produces `com.example.myapp.MyappActivity`.
363
+
364
+ ```xml
365
+ <android xmlns:android="http://schemas.android.com/apk/res/android">
366
+ <manifest>
367
+ <application>
368
+ <activity
369
+ android:name="com.example.myapp.MyappActivity"
370
+ android:screenOrientation="fullSensor"/>
371
+ </application>
372
+ </manifest>
373
+ </android>
374
+ ```
375
+
376
+ To verify the exact generated activity name, check `build/android/AndroidManifest.xml` after a build.
377
+
378
+ ### iOS: support all orientations on iPad
379
+
380
+ ```xml
381
+ <ios>
382
+ <plist>
383
+ <dict>
384
+ <key>UISupportedInterfaceOrientations</key>
385
+ <array>
386
+ <string>UIInterfaceOrientationPortrait</string>
387
+ </array>
388
+ <key>UISupportedInterfaceOrientations~ipad</key>
389
+ <array>
390
+ <string>UIInterfaceOrientationPortrait</string>
391
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
392
+ <string>UIInterfaceOrientationLandscapeLeft</string>
393
+ <string>UIInterfaceOrientationLandscapeRight</string>
394
+ </array>
395
+ </dict>
396
+ </plist>
397
+ </ios>
398
+ ```
399
+
400
+ ## 8. Quick reference
401
+
402
+ | Concept | Titanium API |
403
+ | --- | --- |
404
+ | Window size (split-screen safe) | `$.win.size.width` / `$.win.size.height` after `postlayout` — already in dp |
405
+ | Device screen size | `Ti.Platform.displayCaps.platformWidth` — pixels on Android, needs `/logicalDensityFactor` |
406
+ | Orientation (boolean) | `Ti.Gesture.portrait`, `Ti.Gesture.landscape` — properties, not methods |
407
+ | Orientation event | `Ti.Gesture.addEventListener('orientationchange', handler)` |
408
+ | Window resize event | `$.win.addEventListener('postlayout', handler)` — fires on rotation, split-screen, resize |
409
+ | Auto-size | `Ti.UI.SIZE` (fit content), `Ti.UI.FILL` (fill remaining) |
410
+ | Percentages | `width: '50%'` relative to parent |
411
+ | Spacing | `left`/`top` as offset from previous sibling in horizontal/vertical |
412
+ | Batch updates | `view.applyProperties({...})` to reduce bridge overhead |
413
+
414
+ For detailed layout APIs, see the `ti-ui` skill's [layouts-and-positioning.md](../../ti-ui/references/layouts-and-positioning.md) and [orientation.md](../../ti-ui/references/orientation.md).
@@ -23,12 +23,11 @@ if (OS_ANDROID) {
23
23
  }
24
24
  ```
25
25
 
26
- :::warning These are NOT runtime checks
27
- `OS_IOS` and `OS_ANDROID` are replaced by the Alloy compiler with literal `true`/`false`. The opposite platform's code is completely removed from the build output. This is different from `Ti.Platform.osname === 'iphone'` which remains in the code.
28
-
29
- **Use for:** Platform-specific APIs, platform-specific UI configuration
30
- **Do NOT use for:** Logic that should be testable on both platforms
31
- :::
26
+ > **⚠️ These are NOT runtime checks**
27
+ > `OS_IOS` and `OS_ANDROID` are replaced by the Alloy compiler with literal `true`/`false`. The opposite platform's code is completely removed from the build output. This is different from `Ti.Platform.osname === 'iphone'` which remains in the code.
28
+ >
29
+ > **Use for:** Platform-specific APIs, platform-specific UI configuration
30
+ > **Do NOT use for:** Logic that should be testable on both platforms
32
31
 
33
32
  ### Env_dev / env_test / env_production
34
33
 
@@ -132,9 +131,8 @@ const debug = Alloy.CFG.debug // true in dev, false in prod
132
131
  const appName = Alloy.CFG.appName // From global
133
132
  ```
134
133
 
135
- :::tip CFG is read-only
136
- `Alloy.CFG` values are set at build time and cannot be modified at runtime. For runtime configuration, use `Alloy.Globals` or `Ti.App.Properties`.
137
- :::
134
+ > **💡 CFG is read-only**
135
+ > `Alloy.CFG` values are set at build time and cannot be modified at runtime. For runtime configuration, use `Alloy.Globals` or `Ti.App.Properties`.
138
136
 
139
137
  ---
140
138
 
@@ -256,14 +254,13 @@ const title = $.args.title || ''
256
254
  const showBack = $.args.showBack !== 'false' // XML passes strings
257
255
  ```
258
256
 
259
- :::warning XML attributes are always strings
260
- When passing values through XML attributes, they arrive as strings in `$.args`. Numbers and booleans need to be parsed:
261
- ```javascript
262
- const count = parseInt($.args.count, 10) // "5" → 5
263
- const visible = $.args.visible !== 'false' // "false" → false
264
- ```
265
- For complex data, pass via `Alloy.createController()` in the parent controller instead.
266
- :::
257
+ > **⚠️ XML attributes are always strings**
258
+ > When passing values through XML attributes, they arrive as strings in `$.args`. Numbers and booleans need to be parsed:
259
+ > ```javascript
260
+ > const count = parseInt($.args.count, 10) // "5" → 5
261
+ > const visible = $.args.visible !== 'false' // "false" → false
262
+ > ```
263
+ > For complex data, pass via `Alloy.createController()` in the parent controller instead.
267
264
 
268
265
  ---
269
266
 
@@ -365,9 +362,8 @@ users.fetch({
365
362
  </View>
366
363
  ```
367
364
 
368
- :::warning `if` is evaluated once at controller creation
369
- The `if` attribute is checked when the controller is created. It does NOT react to later changes in the condition. For dynamic visibility, use the `visible` property in the controller.
370
- :::
365
+ > **⚠️ `if` is evaluated once at controller creation**
366
+ > The `if` attribute is checked when the controller is created. It does NOT react to later changes in the condition. For dynamic visibility, use the `visible` property in the controller.
371
367
 
372
368
  ---
373
369