@maccesar/titools 2.2.11 → 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 (99) 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 +106 -138
  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/architecture-tiers.md +248 -0
  68. package/skills/ti-expert/references/code-conventions.md +5 -3
  69. package/skills/ti-expert/references/error-handling.md +14 -7
  70. package/skills/ti-expert/references/examples.md +4 -2
  71. package/skills/ti-expert/references/performance-optimization.md +26 -24
  72. package/skills/ti-expert/references/security-device.md +17 -14
  73. package/skills/ti-expert/references/security-fundamentals.md +60 -101
  74. package/skills/ti-expert/references/state-management.md +15 -14
  75. package/skills/ti-expert/references/testing-e2e-ci.md +30 -21
  76. package/skills/ti-expert/references/theming.md +12 -20
  77. package/skills/ti-guides/SKILL.md +13 -17
  78. package/skills/ti-guides/references/advanced-data-and-images.md +30 -10
  79. package/skills/ti-guides/references/application-frameworks.md +3 -3
  80. package/skills/ti-guides/references/coding-best-practices.md +31 -2
  81. package/skills/ti-guides/references/commonjs-advanced.md +46 -4
  82. package/skills/ti-guides/references/hello-world.md +9 -13
  83. package/skills/ti-guides/references/hyperloop-native-access.md +4 -1
  84. package/skills/ti-guides/references/javascript-primer.md +13 -5
  85. package/skills/ti-guides/references/resources.md +0 -2
  86. package/skills/ti-guides/references/style-and-conventions.md +1 -0
  87. package/skills/ti-guides/references/tiapp-config.md +0 -32
  88. package/skills/ti-howtos/SKILL.md +43 -45
  89. package/skills/ti-howtos/references/buffer-codec-streams.md +25 -3
  90. package/skills/ti-howtos/references/debugging-profiling.md +14 -25
  91. package/skills/ti-howtos/references/google-maps-v2.md +3 -3
  92. package/skills/ti-howtos/references/ios-map-kit.md +8 -1
  93. package/skills/ti-howtos/references/notification-services.md +0 -1
  94. package/skills/ti-howtos/references/webpack-build-pipeline.md +3 -0
  95. package/skills/ti-ui/SKILL.md +47 -49
  96. package/skills/ti-ui/references/application-structures.md +3 -2
  97. package/skills/ti-ui/references/orientation.md +17 -9
  98. package/skills/ti-ui/references/platform-ui-ios.md +63 -0
  99. package/skills/ti-ui/references/scrolling-views.md +39 -0
@@ -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
 
@@ -66,45 +66,44 @@ app/
66
66
 
67
67
  ## lib/ folder and module require paths
68
68
 
69
- :::danger CRITICAL: lib/ Folder is FLATTENED During Build
70
- When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
71
- - `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
72
- - `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
73
-
74
- **Therefore, require statements should NOT include `lib/` prefix:**
75
- ```javascript
76
- // ❌ WRONG - Will fail at runtime
77
- const authApi = require('lib/api/authApi')
78
-
79
- // ✅ CORRECT - Path relative to flattened lib/
80
- const authApi = require('api/authApi')
81
- const authService = require('services/authService')
82
- ```
83
-
84
- **This applies to:**
85
- - All files in `app/lib/` (services, api, helpers, etc.)
86
- - Cross-references within lib/ files
87
- - Controller requires of lib/ files
88
-
89
- **Example project structure:**
90
- ```
91
- app/
92
- ├── lib/
93
- │ ├── services/
94
- │ │ ├── authService.js # require('services/navigationService')
95
- │ │ ├── navigationService.js # require('services/notificationService')
96
- │ │ └── notificationService.js
97
- │ ├── api/
98
- │ │ ├── authApi.js # require('services/authService')
99
- │ │ ├── userApi.js
100
- │ │ └── frameApi.js
101
- │ └── repositories/
102
- │ ├── userRepository.js
103
- │ └── settingsRepository.js
104
- ├── controllers/
105
- │ └── index.js # require('services/authService')
106
- ```
107
- :::
69
+ > **🚨 CRITICAL: lib/ Folder is FLATTENED During Build**
70
+ > When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
71
+ > - `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
72
+ > - `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
73
+ >
74
+ > **Therefore, require statements should NOT include `lib/` prefix:**
75
+ > ```javascript
76
+ > // ❌ WRONG - Will fail at runtime
77
+ > const authApi = require('lib/api/authApi')
78
+ >
79
+ > // ✅ CORRECT - Path relative to flattened lib/
80
+ > const authApi = require('api/authApi')
81
+ > const authService = require('services/authService')
82
+ > ```
83
+ >
84
+ > **This applies to:**
85
+ > - All files in `app/lib/` (services, api, helpers, etc.)
86
+ > - Cross-references within lib/ files
87
+ > - Controller requires of lib/ files
88
+ >
89
+ > **Example project structure:**
90
+ > ```
91
+ > app/
92
+ > ├── lib/
93
+ > │ ├── services/
94
+ > │ │ ├── authService.js # require('services/navigationService')
95
+ > │ │ ├── navigationService.js # require('services/notificationService')
96
+ > │ │ └── notificationService.js
97
+ > │ ├── api/
98
+ > │ │ ├── authApi.js # require('services/authService')
99
+ > │ │ ├── userApi.js
100
+ > │ │ └── frameApi.js
101
+ > │ └── repositories/
102
+ > │ ├── userRepository.js
103
+ > │ └── settingsRepository.js
104
+ > ├── controllers/
105
+ > │ └── index.js # require('services/authService')
106
+ > ```
108
107
 
109
108
  ## Data layer: two approaches
110
109
 
@@ -338,9 +337,8 @@ const loadData = () => {
338
337
  }
339
338
  ```
340
339
 
341
- :::tip Widget Styles
342
- Widgets have their own `styles/widget.tss` file. Define all widget-specific styles there to keep them self-contained and portable.
343
- :::
340
+ > **💡 Widget Styles**
341
+ > Widgets have their own `styles/widget.tss` file. Define all widget-specific styles there to keep them self-contained and portable.
344
342
 
345
343
  ### Widget ↔ controller communication
346
344
 
@@ -76,6 +76,39 @@
76
76
  - iOS: Use `Ti.UI.iOS.createDocumentViewer` for files, or simple `Ti.UI.createOptionDialog` + `Ti.UI.Clipboard` for links
77
77
  - Android: Use `Ti.Android.createIntent` with ACTION_SEND
78
78
 
79
+ ## Community-Discovered Patterns
80
+
81
+ ### 15. Using extendEdges without autoAdjustScrollViewInsets (iOS)
82
+
83
+ **Anti-pattern:** Setting `extendEdges: [Ti.UI.EXTEND_EDGE_ALL]` on a Window without also setting `autoAdjustScrollViewInsets: true`.
84
+
85
+ ```javascript
86
+ // Wrong: content overlaps behind navigation bar
87
+ const win = Ti.UI.createWindow({
88
+ title: 'My Screen',
89
+ largeTitleEnabled: true,
90
+ extendEdges: [Ti.UI.EXTEND_EDGE_ALL]
91
+ // missing autoAdjustScrollViewInsets!
92
+ });
93
+ ```
94
+
95
+ **Why it's wrong:** `extendEdges` tells iOS to extend the view's content behind the navigation bar and tab bar. Without `autoAdjustScrollViewInsets: true`, iOS does not adjust the ScrollView's content insets, so the scroll content starts at y=0 — directly behind the navigation bar.
96
+
97
+ **Fix:** Always pair `extendEdges` with `autoAdjustScrollViewInsets`:
98
+
99
+ ```javascript
100
+ const win = Ti.UI.createWindow({
101
+ title: 'My Screen',
102
+ largeTitleEnabled: true,
103
+ extendEdges: [Ti.UI.EXTEND_EDGE_ALL],
104
+ autoAdjustScrollViewInsets: true
105
+ });
106
+ ```
107
+
108
+ These three properties work together: `extendEdges` creates the blur/translucent effect, `autoAdjustScrollViewInsets` prevents content overlap, and `largeTitleEnabled` shows the collapsible large title. Without `extendEdges`, the large title also renders with a visible delay (empty nav bar gap appears first, then the title draws).
109
+
110
+ This applies to Windows inside both standalone NavigationWindow and TabGroup (which wraps each Tab in an implicit NavigationWindow on iOS).
111
+
79
112
  ---
80
113
 
81
114
  ## Quick reference table
@@ -88,3 +121,4 @@
88
121
  | `lib/` prefix | lib/ is flattened | Use path without lib/ |
89
122
  | `$.index.open()` | Wrong ID reference | Use actual Window ID |
90
123
  | `createNotification` | API doesn't exist | `createAlertDialog` |
124
+ | `extendEdges` alone | Content behind nav bar | Add `autoAdjustScrollViewInsets: true` |