@maccesar/titools 2.7.2 → 2.9.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 (51) hide show
  1. package/README.md +6 -13
  2. package/lib/config.js +1 -1
  3. package/package.json +1 -1
  4. package/skills/purgetss/SKILL.md +25 -0
  5. package/skills/purgetss/references/EXAMPLES.md +86 -24
  6. package/skills/purgetss/references/app-branding.md +412 -0
  7. package/skills/purgetss/references/appearance-module.md +161 -0
  8. package/skills/purgetss/references/apply-directive.md +87 -31
  9. package/skills/purgetss/references/arbitrary-values.md +4 -0
  10. package/skills/purgetss/references/class-categories.md +10 -7
  11. package/skills/purgetss/references/class-index.md +25 -18
  12. package/skills/purgetss/references/cli-commands.md +219 -8
  13. package/skills/purgetss/references/configurable-properties.md +11 -7
  14. package/skills/purgetss/references/custom-rules.md +7 -3
  15. package/skills/purgetss/references/customization-deep-dive.md +52 -4
  16. package/skills/purgetss/references/dynamic-component-creation.md +29 -14
  17. package/skills/purgetss/references/grid-layout.md +20 -8
  18. package/skills/purgetss/references/icon-fonts.md +4 -0
  19. package/skills/purgetss/references/installation-setup.md +3 -8
  20. package/skills/purgetss/references/ios-large-titles.md +141 -0
  21. package/skills/purgetss/references/migration-guide.md +162 -25
  22. package/skills/purgetss/references/multi-density-images.md +363 -0
  23. package/skills/purgetss/references/opacity-modifier.md +4 -0
  24. package/skills/purgetss/references/performance-tips.md +5 -0
  25. package/skills/purgetss/references/platform-modifiers.md +4 -0
  26. package/skills/purgetss/references/semantic-colors.md +386 -0
  27. package/skills/purgetss/references/smart-mappings.md +50 -28
  28. package/skills/purgetss/references/tikit-components.md +3 -1
  29. package/skills/purgetss/references/titanium-resets.md +46 -15
  30. package/skills/purgetss/references/ui-ux-design.md +32 -6
  31. package/skills/ti-branding/SKILL.md +0 -230
  32. package/skills/ti-branding/assets/ic_launcher.xml +0 -6
  33. package/skills/ti-branding/references/android-adaptive-icons.md +0 -85
  34. package/skills/ti-branding/references/cleanup-legacy.md +0 -112
  35. package/skills/ti-branding/references/ios-appiconset.md +0 -62
  36. package/skills/ti-branding/references/master-input-guidelines.md +0 -84
  37. package/skills/ti-branding/references/notification-icons.md +0 -63
  38. package/skills/ti-branding/references/splash-screen-api.md +0 -81
  39. package/skills/ti-branding/references/ti-icon-paths.md +0 -84
  40. package/skills/ti-branding/references/tiapp-xml-snippets.md +0 -92
  41. package/skills/ti-branding/scripts/lib/cleanup-legacy.sh +0 -230
  42. package/skills/ti-branding/scripts/lib/deps.sh +0 -58
  43. package/skills/ti-branding/scripts/lib/gen-android-adaptive.sh +0 -64
  44. package/skills/ti-branding/scripts/lib/gen-android-legacy.sh +0 -36
  45. package/skills/ti-branding/scripts/lib/gen-ios.sh +0 -46
  46. package/skills/ti-branding/scripts/lib/gen-marketplace.sh +0 -55
  47. package/skills/ti-branding/scripts/lib/gen-notification.sh +0 -46
  48. package/skills/ti-branding/scripts/lib/gen-splash-icon.sh +0 -31
  49. package/skills/ti-branding/scripts/lib/prepare-master.sh +0 -48
  50. package/skills/ti-branding/scripts/lib/validate.sh +0 -67
  51. package/skills/ti-branding/scripts/ti-branding +0 -546
@@ -0,0 +1,386 @@
1
+ # Semantic Colors
2
+
3
+ Semantic colors let your app respond to Light / Dark mode changes without any extra runtime code. You define color **names** once, give each name a `light` and `dark` hex value, and Titanium resolves the right one at render time based on `Ti.UI.overrideUserInterfaceStyle`. PurgeTSS's contribution is the `theme.extend.colors` mapping in `config.cjs` that turns those names into utility classes like `bg-surface` or `text-on-surface`.
4
+
5
+ This file covers the full workflow: the JSON schema, the `config.cjs` mapping, nesting rules (and the `[object Object]` trap), the numeric 11-step tonal-inversion pattern, alpha transparency, the `purgetss semantic` CLI, and three patterns for consuming semantic colors from controllers at runtime.
6
+
7
+ For the mode-switching runtime that drives all of this (`Appearance.init()`, `Appearance.set(...)`, persistence), see [appearance-module.md](./appearance-module.md).
8
+
9
+ > **INFO**
10
+ >
11
+ > Semantic color resolution is a **Titanium** feature, not PurgeTSS magic. Titanium natively reads `semantic.colors.json` (at `app/assets/` on Alloy, `Resources/` on Classic) and resolves any color-accepting property whose value matches a key in that file. PurgeTSS only generates the utility classes that point *at* those keys — the switching behavior is 100% native.
12
+
13
+ ## Setting up `semantic.colors.json`
14
+
15
+ Create the `semantic.colors.json` file with your color definitions. The file location depends on your project type, per the TiDev convention:
16
+
17
+ - **Alloy** → `app/assets/semantic.colors.json`
18
+ - **Classic** → `Resources/semantic.colors.json`
19
+
20
+ > **INFO**
21
+ >
22
+ > The `semantic` command (covered later on this page) auto-detects the project layout and writes to the right location — you don't need to specify it manually. The path examples below use the Alloy location; Classic users get their output under `Resources/` automatically.
23
+
24
+ Each top-level key is a color name; each value is an object with `light` and `dark` hex strings.
25
+
26
+ `app/assets/semantic.colors.json`
27
+ ```json
28
+ {
29
+ "surfaceColor": {
30
+ "light": "#F9FAFB",
31
+ "dark": "#0f172a"
32
+ },
33
+ "surfaceHighColor": {
34
+ "light": "#FFFFFF",
35
+ "dark": "#1e293b"
36
+ },
37
+ "textColor": {
38
+ "light": "#111827",
39
+ "dark": "#f1f5f9"
40
+ },
41
+ "textSecondaryColor": {
42
+ "light": "#6B7280",
43
+ "dark": "#94a3b8"
44
+ },
45
+ "borderColor": {
46
+ "light": "#E5E7EB",
47
+ "dark": "#334155"
48
+ },
49
+ "accentColor": {
50
+ "light": "#3B82F6",
51
+ "dark": "#60a5fa"
52
+ }
53
+ }
54
+ ```
55
+
56
+ This is a reasonable 6-color starter palette: background, elevated surfaces, primary text, secondary text, borders, and an accent. Most apps can ship with just these and extend later.
57
+
58
+ ### Alpha transparency — the 8-digit format
59
+
60
+ Titanium accepts the `#RRGGBBAA` 8-digit hex format anywhere a color is expected. Use it in either mode to produce translucent surfaces like overlays or glass sheets:
61
+
62
+ ```json
63
+ {
64
+ "overlayColor": {
65
+ "light": "#00000080",
66
+ "dark": "#000000CC"
67
+ },
68
+ "glassColor": {
69
+ "light": "#FFFFFFB3",
70
+ "dark": "#0F172AB3"
71
+ }
72
+ }
73
+ ```
74
+
75
+ The last two hex digits are the alpha channel: `00` is fully transparent, `FF` is fully opaque, `80` is ~50%, `B3` is ~70%, `CC` is ~80%.
76
+
77
+ ## Registering in `config.cjs`
78
+
79
+ Titanium resolves `surfaceColor` at runtime, but PurgeTSS needs to know which **class name** should emit that color. Map the JSON keys to class names under `theme.extend.colors`:
80
+
81
+ `purgetss/config.cjs`
82
+ ```js
83
+ module.exports = {
84
+ theme: {
85
+ extend: {
86
+ colors: {
87
+ surface: {
88
+ DEFAULT: 'surfaceColor',
89
+ high: 'surfaceHighColor'
90
+ },
91
+ 'on-surface': 'textColor',
92
+ 'on-surface-variant': 'textSecondaryColor',
93
+ border: 'borderColor',
94
+ accent: 'accentColor'
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ This generates `bg-surface`, `bg-surface-high`, `text-on-surface`, `text-on-surface-variant`, `bg-border`, `text-accent`, `bg-accent`, etc. Any utility that takes a color — `bg-*`, `text-*`, `border-*`, `placeholder-*`, tint classes — can reference the name.
102
+
103
+ ### Nested pattern with `DEFAULT`
104
+
105
+ One level of nesting is supported, and it must include a `DEFAULT` key for the base variant:
106
+
107
+ ```js
108
+ // Correct -- generates bg-surface and bg-surface-high
109
+ surface: {
110
+ DEFAULT: 'surfaceColor',
111
+ high: 'surfaceHighColor'
112
+ }
113
+ ```
114
+
115
+ `bg-surface` resolves to `surfaceColor` (the `DEFAULT`), and `bg-surface-high` resolves to `surfaceHighColor`.
116
+
117
+ > **DANGER**
118
+ >
119
+ > Common error: nested objects without `DEFAULT`
120
+ >
121
+ > ```js
122
+ > // Wrong -- generates [object Object] instead of a color
123
+ > surface: {
124
+ > regular: 'surfaceColor',
125
+ > high: 'surfaceHighColor'
126
+ > }
127
+ > ```
128
+ >
129
+ > If you nest without a `DEFAULT` key and then use the base class (`bg-surface`), PurgeTSS serializes the whole nested object with `String(...)`, producing the literal string `[object Object]` as the color value. Titanium can't parse that, and the view renders with whatever fallback the platform has. Always include `DEFAULT` for the base variant, or use a flat structure.
130
+
131
+ ### Flat structure alternative
132
+
133
+ If you prefer not to nest at all, every class name is a top-level key:
134
+
135
+ ```js
136
+ colors: {
137
+ surface: 'surfaceColor',
138
+ 'surface-high': 'surfaceHighColor',
139
+ 'on-surface': 'textColor',
140
+ border: 'borderColor',
141
+ accent: 'accentColor'
142
+ }
143
+ ```
144
+
145
+ Both approaches work. The nested form groups related shades under one namespace; the flat form avoids the `DEFAULT` trap. Pick whichever reads better in your `config.cjs`.
146
+
147
+ ## Numeric 11-step tonal-inversion palette
148
+
149
+ Instead of purpose-based names, you can model a color as an 11-step tonal scale (`50` through `950`) where each light-mode value **inverts** in dark mode. This gives you a full tonal range from a single palette and keeps dark-mode contrast in lockstep with light mode.
150
+
151
+ `app/assets/semantic.colors.json`
152
+ ```json
153
+ {
154
+ "color50": { "light": "#030712", "dark": "#f9fafb" },
155
+ "color100": { "light": "#111827", "dark": "#f3f4f6" },
156
+ "color200": { "light": "#1f2937", "dark": "#e5e7eb" },
157
+ "color300": { "light": "#374151", "dark": "#d1d5db" },
158
+ "color400": { "light": "#4b5563", "dark": "#9ca3af" },
159
+ "color500": { "light": "#6b7280", "dark": "#6b7280" },
160
+ "color600": { "light": "#9ca3af", "dark": "#4b5563" },
161
+ "color700": { "light": "#d1d5db", "dark": "#374151" },
162
+ "color800": { "light": "#e5e7eb", "dark": "#1f2937" },
163
+ "color900": { "light": "#f3f4f6", "dark": "#111827" },
164
+ "color950": { "light": "#f9fafb", "dark": "#030712" }
165
+ }
166
+ ```
167
+
168
+ Mapped as a nested `primary` palette:
169
+
170
+ `purgetss/config.cjs`
171
+ ```js
172
+ module.exports = {
173
+ theme: {
174
+ extend: {
175
+ colors: {
176
+ primary: {
177
+ 50: 'color50',
178
+ 100: 'color100',
179
+ 200: 'color200',
180
+ 300: 'color300',
181
+ 400: 'color400',
182
+ 500: 'color500',
183
+ 600: 'color600',
184
+ 700: 'color700',
185
+ 800: 'color800',
186
+ 900: 'color900',
187
+ 950: 'color950'
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ You get `bg-primary-50`, `text-primary-950`, `border-primary-500`, etc. — and the tonal contrast automatically flips with the appearance.
196
+
197
+ ### How the inversion works
198
+
199
+ | Role | Light | Dark | Notes |
200
+ | --------------------- | --------- | --------- | ------------------------------------------- |
201
+ | `color50` (extreme) | `#030712` | `#f9fafb` | Darkest in light mode, lightest in dark |
202
+ | `color500` (middle) | `#6b7280` | `#6b7280` | Anchor — identical in both modes |
203
+ | `color950` (mirror) | `#f9fafb` | `#030712` | Mirror of `color50` — extremes reversed |
204
+
205
+ The stops mirror by index: `50` <-> `950`, `100` <-> `900`, `200` <-> `800`, and so on. `500` is the anchor and is identical in both modes.
206
+
207
+ > **INFO**
208
+ >
209
+ > The numeric pattern and the purpose-based pattern can coexist in the same project. A common layout is: numeric scale for one brand family (`primary`, `secondary`), purpose-based names for surfaces and text (`surfaceColor`, `textColor`, `borderColor`). Use whichever expresses intent most clearly per role.
210
+
211
+ ## The `semantic` CLI command
212
+
213
+ Writing the 11 JSON entries by hand (palette) or each purpose-based color (single) is mechanical and error-prone. The `purgetss semantic` command does both. Dispatch is by `--single` — omit it for palette mode, include it for single mode.
214
+
215
+ ### Palette mode — auto-generated tonal scale
216
+
217
+ One base hex plus a family name produces 11 JSON entries with mirror inversion, plus a matching `config.cjs` mapping, in one step:
218
+
219
+ ```bash
220
+ purgetss semantic '#15803d' amazon
221
+ ```
222
+
223
+ This:
224
+
225
+ 1. Generates `amazon50` through `amazon950` using the same algorithm as the `shades` command.
226
+ 2. Writes `semantic.colors.json` at the project's canonical location (`app/assets/` on Alloy, `Resources/` on Classic) with mirror-by-index values — 50 ↔ 950, 100 ↔ 900, …, 500 as the identical anchor.
227
+ 3. Writes the `{ 50: 'amazon50', 100: 'amazon100', ... }` mapping into `config.cjs`.
228
+ 4. Strips any prior keys for the `amazon` family before writing — re-runs cleanly replace, never duplicate.
229
+
230
+ Useful flags:
231
+
232
+ - `--log` (`-l`) — preview the JSON on the console without writing anything.
233
+ - `--override` (`-o`) — place the mapping in `theme.colors` instead of `theme.extend.colors`.
234
+ - `--random` (`-r`) with `--name` (`-n`) — pick a random base color for a named family.
235
+
236
+ ```bash
237
+ purgetss semantic '#15803d' amazon --log # preview only
238
+ purgetss semantic '#15803d' amazon --override # goes into theme.colors
239
+ purgetss semantic --random --name brand # random base, named family
240
+ ```
241
+
242
+ ### Single mode — purpose-based with explicit per-mode hex
243
+
244
+ For colors like `surfaceColor`, `textColor`, `borderColor`, or `overlayColor` — where light and dark values are hand-picked from the design system, not derived algorithmically. Pass `--single`, the light hex, the name, and optionally `--dark` and `--alpha`:
245
+
246
+ ```bash
247
+ purgetss semantic --single '#F9FAFB' surfaceColor --dark '#0f172a'
248
+ purgetss semantic --single '#FFFFFF' surfaceHighColor --dark '#1e293b'
249
+ purgetss semantic --single '#111827' textColor --dark '#f1f5f9'
250
+ purgetss semantic --single '#6B7280' textSecondaryColor --dark '#94a3b8'
251
+ purgetss semantic --single '#E5E7EB' borderColor --dark '#334155'
252
+ purgetss semantic --single '#3B82F6' accentColor --dark '#60a5fa'
253
+ purgetss semantic --single '#000000' overlayColor --alpha 50
254
+ ```
255
+
256
+ The name is preserved verbatim as the JSON key (camelCase is respected). When `--dark` is omitted, it defaults to the light hex — useful for overlays where alpha is the only variation between modes.
257
+
258
+ Single mode writes **both files** in one shot. The class name is auto-derived from the semantic key by stripping the conventional `Color` suffix and kebab-casing the rest, so `surfaceHighColor` becomes the `surface-high` class:
259
+
260
+ `./purgetss/config.cjs` (auto-generated)
261
+ ```js
262
+ theme: {
263
+ extend: {
264
+ colors: {
265
+ surface: 'surfaceColor',
266
+ 'surface-high': 'surfaceHighColor',
267
+ text: 'textColor',
268
+ 'text-secondary': 'textSecondaryColor',
269
+ border: 'borderColor',
270
+ accent: 'accentColor',
271
+ overlay: 'overlayColor'
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ After the batch above you can use `bg-surface`, `bg-surface-high`, `text-text`, `bg-accent`, `bg-overlay`, etc. immediately. If your design system uses different class names (for example `on-surface` instead of `text`, or the nested `surface: { DEFAULT, high }` form from earlier in this page), edit `config.cjs` after running the commands.
278
+
279
+ > **INFO**
280
+ >
281
+ > Smart in-place updates
282
+ >
283
+ > If a `--single` name matches an existing palette shade — for example `pt semantic --single '#000' amazon500` while the `amazon` palette exists — the entry is updated in place in the JSON (preserving its position) and `config.cjs` is left untouched. The palette already maps to that key, so the operation is interpreted as "edit one shade", not "create a duplicate top-level color".
284
+
285
+ ### Alpha details
286
+
287
+ Alpha follows the Titanium spec exactly: range `0.0-100.0`, stored as a **string**, wrapped per-mode as `{ color, alpha }`. Without `--alpha`, values stay as bare hex strings. Out-of-range values are rejected before any file is written.
288
+
289
+ ## Using semantic classes in views
290
+
291
+ Once the JSON and the `config.cjs` mapping are in place, you use the semantic classes like any other PurgeTSS utility:
292
+
293
+ ```xml
294
+ <Window class="bg-surface" title="Settings">
295
+ <ScrollView class="vertical content-w-screen content-h-auto">
296
+ <Label class="text-on-surface font-bold" text="Title" />
297
+ <Label class="text-on-surface-variant text-sm" text="Subtitle" />
298
+ <View class="h-px w-screen bg-border" />
299
+ </ScrollView>
300
+ </Window>
301
+ ```
302
+
303
+ When the appearance changes — whether from `Appearance.set(...)` or a system-level toggle — Titanium resolves each semantic color name to its `light` or `dark` value automatically. No event listeners, no manual repaint.
304
+
305
+ ## Using semantic colors in controllers
306
+
307
+ Semantic colors also work from JavaScript. Three patterns cover the cases you'll hit.
308
+
309
+ ### Option 1 — Direct assignment by semantic name
310
+
311
+ Titanium resolves the semantic name at runtime, so you can assign it straight to any color-accepting property. This skips PurgeTSS entirely and is the shortest path when you only need one or two changes:
312
+
313
+ ```js
314
+ $.titleLabel.color = 'textColor'
315
+ $.card.backgroundColor = 'surfaceHighColor'
316
+ $.divider.backgroundColor = 'borderColor'
317
+ ```
318
+
319
+ ### Option 2 — `$.UI.create()` with PurgeTSS classes
320
+
321
+ When you build a component dynamically and want the full set of utilities (colors included), use `$.UI.create()`:
322
+
323
+ ```js
324
+ const card = $.UI.create('View', {
325
+ classes: ['bg-surface-high', 'rounded-lg', 'mx-4', 'my-2']
326
+ })
327
+
328
+ const title = $.UI.create('Label', {
329
+ text: 'Settings',
330
+ classes: ['text-on-surface', 'font-bold', 'text-lg']
331
+ })
332
+
333
+ card.add(title)
334
+ ```
335
+
336
+ ### Option 3 — `Alloy.createStyle()` + `applyProperties()`
337
+
338
+ To swap styles on an **existing** component — reacting to a state change, for example — build the style and apply it:
339
+
340
+ ```js
341
+ const setActive = (isActive) => {
342
+ const style = Alloy.createStyle('index', {
343
+ apiName: 'Ti.UI.Label',
344
+ classes: isActive
345
+ ? ['text-accent', 'font-bold']
346
+ : ['text-on-surface-variant', 'font-normal']
347
+ })
348
+
349
+ $.statusLabel.applyProperties(style)
350
+ }
351
+ ```
352
+
353
+ > **INFO**
354
+ >
355
+ > When to use which
356
+ >
357
+ > - **Option 1** — single property change, no other utilities needed.
358
+ > - **Option 2** — creating new components from scratch.
359
+ > - **Option 3** — restyling components that already exist in the view.
360
+
361
+ ## Recommended starter palette
362
+
363
+ A minimal semantic palette that covers most app surfaces:
364
+
365
+ | Purpose | Semantic name | Light | Dark | Classes generated |
366
+ | ------------------ | -------------------- | --------- | --------- | -------------------------- |
367
+ | Background | `surfaceColor` | `#F9FAFB` | `#0f172a` | `bg-surface` |
368
+ | Cards / elevated | `surfaceHighColor` | `#FFFFFF` | `#1e293b` | `bg-surface-high` |
369
+ | Primary text | `textColor` | `#111827` | `#f1f5f9` | `text-on-surface` |
370
+ | Secondary text | `textSecondaryColor` | `#6B7280` | `#94a3b8` | `text-on-surface-variant` |
371
+ | Borders / dividers | `borderColor` | `#E5E7EB` | `#334155` | `bg-border` |
372
+ | Accent | `accentColor` | `#3B82F6` | `#60a5fa` | `text-accent`, `bg-accent` |
373
+
374
+ Start with these 5-6 colors and add more only when the design requires it. Fewer semantic colors means easier maintenance.
375
+
376
+ ## Related
377
+
378
+ - [appearance-module.md](./appearance-module.md) — `Appearance.init()`, `set(...)`, `get()`, `toggle()` — the runtime that switches the whole palette.
379
+ - [cli-commands.md#semantic-command](./cli-commands.md#semantic-command) — full reference for `purgetss semantic` (palette and single modes, all flags).
380
+ - [customization-deep-dive.md](./customization-deep-dive.md) — full `config.cjs` structure, including `theme.extend` vs `theme.colors` and the rest of the extendable keys.
381
+
382
+ ## Community-Discovered Patterns
383
+
384
+ - **The `[object Object]` crash is always a missing `DEFAULT`.** Any time a view renders with a mysterious fallback color and the class was something like `bg-surface`, check `config.cjs` first: if `surface` is a nested object without a `DEFAULT` key, PurgeTSS serializes the whole object and emits `[object Object]` as the color value. The fix is either to add `DEFAULT: 'surfaceColor'` inside the nested object or flatten the structure.
385
+ - **Semantic resolution is Titanium-native, not PurgeTSS.** This matters when debugging: if a semantic name doesn't resolve, the problem is usually the `semantic.colors.json` file (wrong filename, wrong key, malformed JSON, or file in the wrong location — Alloy expects `app/assets/`, Classic expects `Resources/`), not PurgeTSS. PurgeTSS's job ends at generating the class that points at the name.
386
+ - **Re-running `purgetss semantic` on the same family is safe.** The CLI strips prior keys for that family from both the JSON and `config.cjs` before writing, so switching between palette and single forms — or changing the base hex — does not leave orphans. Other palettes and manually-defined entries are untouched.
@@ -1,30 +1,52 @@
1
1
  # Smart Mappings & Platform Realities
2
2
 
3
- PurgeTSS maps CSS-like utilities to native Titanium properties. Understanding these "Smart Mappings" is key to predictable layouts and cross-platform consistency.
4
-
5
- ## 1. The "Gap" Utility (Double Spacing)
6
- In PurgeTSS, `gap` is mapped to **external margins** (`top`, `right`, `bottom`, `left`) because Titanium lacks a native CSS-style gap property.
7
- - **Behavior**: `.gap-4` adds 16dp to **all** sides of the element.
8
- - **Critical Difference**: In web CSS, gap only exists *between* elements. In PurgeTSS, two siblings with `.gap-4` will have **32dp** of space between them (16dp from each).
9
- - **Solution**: Use `.gap-x-` or `.gap-y-` for more granular control, or use specific margins (`ml-`, `mt-`) if you need exact CSS-style spacing.
10
-
11
- ## 2. Hybrid Shadows (iOS vs. Android)
12
- Shadow utilities (`.shadow-md`, etc.) provide a cross-platform visual depth by mapping different native properties in a single class:
13
- - **iOS**: Uses `viewShadowOffset`, `viewShadowRadius`, and `viewShadowColor`.
14
- - **Android**: Uses the `elevation` property.
15
- - **Z-Order Warning**: On Android, `elevation` also affects the **Z-order** (stacking) of the view, causing it to appear on top of siblings without elevation. This behavior does **not** happen on iOS.
16
-
17
- ## 3. Grid Container Width
18
- The `.grid` and `.grid-flow-col` classes automatically include `width: '100%'`.
19
- - **Why**: Since PurgeTSS resets `View` to `SIZE`, a grid container must be forced to `100%` (or `FILL`) so that children using percentage widths (like `.col-span-6`) have a parent dimension to calculate against.
20
- - **Manual Overrides**: If you need a grid that only occupies the size of its content, you must explicitly use `wh-auto`.
21
-
22
- ## 4. Native Rotations
23
- The `.rotate-X` utilities map directly to Titanium's `rotate` property.
24
- - **Anchor**: By default, Titanium rotates around the **center** of the component.
25
- - **Matrix**: Unlike web CSS where transformations are often chained (e.g., `transform: rotate(45deg) scale(1.5)`), PurgeTSS/Titanium treats these as direct, separate properties of the view.
26
-
27
- ## 5. Z-Index vs Stacking Order
28
- - **Class**: `.z-10`, `.z-50`, etc.
29
- - **Mapping**: Maps directly to Titanium's `zIndex` property.
30
- - **Note**: In Titanium, the order in which views are **added** to a parent determines their base stacking order (last added is on top). `zIndex` should be used sparingly for explicit overlays.
3
+ Platform-bridge knowledge: how PurgeTSS utility classes translate to Titanium native properties under the hood. This is a curated synthesis class names resemble Tailwind, but the values they emit target Titanium SDK properties (`zIndex`, `viewShadowOffset`, `elevation`, `anchorPoint`, `rotate`, `layout`, etc.), not CSS.
4
+
5
+ For the full inventory of every available class, see [`class-index.md`](./class-index.md) and [`class-categories.md`](./class-categories.md).
6
+
7
+ ## Community-Discovered Patterns
8
+
9
+ ### 1. The "Gap" Utility (External Margins, Not CSS Gap)
10
+
11
+ In PurgeTSS, `gap` is mapped to **external margins** on the element itself (`top`, `right`, `bottom`, `left`) because Titanium lacks a native CSS-style gap property.
12
+
13
+ - **Behavior**: `.gap-4` emits `{ top: 16, right: 16, bottom: 16, left: 16 }` on the element — 16dp of margin on **all four sides**.
14
+ - **Critical Difference from CSS**: In web CSS, `gap` only renders *between* flex/grid children. Here, two siblings each carrying `.gap-4` will show **32dp** of total space between them (16dp contributed by each).
15
+ - **Directional variants** (verified in `lib/templates/` compiled `dist/utilities.tss`):
16
+ - `.gap-x-{size}` — emits `right` and `left` only (horizontal margins).
17
+ - `.gap-y-{size}` emits `top` and `bottom` only (vertical margins).
18
+ - `.gap-{t|r|b|l}-{size}` single-side gap (e.g. `.gap-t-4`).
19
+ - **Evidence**: Official [`purgetss-docs-context7/docs/grid-system.md`](https://github.com/macCesar/purgeTSS) § "Gutter utilities".
20
+
21
+ ### 2. Hybrid Shadows (iOS vs. Android in One Class)
22
+
23
+ Shadow utilities (`.shadow-xs`, `.shadow-sm`, `.shadow`, `.shadow-md`, `.shadow-lg`, `.shadow-xl`, `.shadow-2xl`, `.shadow-inner`, `.shadow-outline`, `.shadow-none`) emit cross-platform depth by setting iOS and Android properties simultaneously:
24
+
25
+ - **iOS**: `viewShadowOffset`, `viewShadowRadius`, `viewShadowColor`.
26
+ - **Android**: `elevation`.
27
+ - **Z-Order Warning (Android-only)**: On Android, `elevation` doubles as a stacking-order hint — an elevated view renders *on top of* siblings with lower or no elevation. iOS shadows do **not** affect z-order. If you apply `.shadow-md` to one card in a list, it may unexpectedly cover adjacent cards on Android.
28
+ - **Evidence**: `dist/utilities.tss` (see `'.shadow-md': { viewShadowOffset: ..., elevation: 24 }`), cross-referenced against Titanium SDK `Ti.UI.View` property behavior.
29
+
30
+ ### 3. Grid Container Auto-Width
31
+
32
+ The `.grid` and `.grid-flow-col` classes emit **both** `layout: 'horizontal'` **and** `width: '100%'` (`.grid-flow-row` emits `layout: 'vertical', height: '100%'`).
33
+
34
+ - **Why the width is baked in**: PurgeTSS resets `View` defaults to `SIZE`. A grid container needs an explicit dimension so that percentage-based children (e.g. `.col-span-6` emitting `width: '50%'`) have a parent measurement to resolve against.
35
+ - **Official docs** ([`grid-system.md`](https://github.com/macCesar/purgeTSS)) only document the `layout: 'horizontal'` half. The `width: '100%'` behavior is **verified against `lib/templates/` → `dist/utilities.tss`** (lines emit `'.grid': { layout: 'horizontal', width: '100%' }`).
36
+ - **Manual override**: If you need a grid sized to its content only, pair it with `.wh-auto` or an explicit sizing utility.
37
+
38
+ ### 4. Native Rotations via `rotate` (Not CSS Transform)
39
+
40
+ The `.rotate-{n}` utilities (`rotate-0`, `rotate-1`, `rotate-2`, `rotate-3`, `rotate-6`, `rotate-12`, `rotate-45`, `rotate-90`, `rotate-135`, `rotate-180`, `rotate-225`, `rotate-270`, `rotate-315`, `rotate-360`) map **directly** to Titanium's `rotate` property — a plain numeric degree value on the view.
41
+
42
+ - **Default anchor**: Titanium rotates around the view's **center**. To change this, use an `.origin-*` utility (`.origin-top`, `.origin-top-left`, `.origin-center`, `.origin-bottom-right`, etc.) which emits `anchorPoint: { x, y }`.
43
+ - **No chained transforms**: Unlike CSS (`transform: rotate(45deg) scale(1.5)`), Titanium treats each transformation as an independent view property. For combined transforms you must build a `Ti.UI.2DMatrix`/`Ti.UI.Matrix2D` and assign it to `transform`.
44
+ - **Evidence**: `dist/utilities.tss` lines `'.rotate-45': { rotate: 45 }` and `'.origin-center': { anchorPoint: { x: 0.5, y: 0.5 } }`.
45
+
46
+ ### 5. Z-Index Uses the Full `z-index-*` Prefix
47
+
48
+ - **Classes**: `.z-index-0`, `.z-index-10`, `.z-index-20`, `.z-index-30`, `.z-index-40`, `.z-index-50` (six values total).
49
+ - **Mapping**: Each emits the Titanium `zIndex` property with the numeric suffix (`.z-index-10` → `{ zIndex: 10 }`).
50
+ - **Important — no Tailwind-style `z-10` shorthand exists.** The full `z-index-` prefix is required. See [`class-index.md`](./class-index.md) (under `zIndex`) and [`class-categories.md`](./class-categories.md) (row "`z-index-*`").
51
+ - **Titanium stacking note**: In a Titanium parent, the **order in which children are added** sets the base stacking (last `add()`ed is on top). `zIndex` should be used sparingly for explicit overlays; for Android, remember that `elevation` (from `.shadow-*` or `.elevation-*`) also influences rendering order.
52
+ - **Evidence**: `dist/utilities.tss` lines 7952–7957.
@@ -316,7 +316,9 @@ Colors: `black`, `dark`, `light`, and `white` themes that affect background and
316
316
 
317
317
  Use this for code examples. Set `copy="true"` to add a copy button.
318
318
 
319
- **Pro tip:** For best results, install a monospaced font (like Fira Code or JetBrains Mono) and configure `font-mono` in your PurgeTSS config.
319
+ > **Community-Discovered Pattern Pro tip**
320
+ >
321
+ > For best results, install a monospaced font (like Fira Code or JetBrains Mono) and configure `font-mono` in your PurgeTSS config.
320
322
 
321
323
  **Localization:** The copy button uses `L('copy', 'Copy')` for its title and `L('code_copied', 'Code copied!')` for the confirmation message. Add these keys to your `strings.xml` files for translation.
322
324
 
@@ -30,16 +30,7 @@ The most important reset is for the `View` element. By default, `View` is set to
30
30
  - Grow to accommodate explicit dimensions (`w-64`, `h-32`, etc.)
31
31
  - Expand to fill margins (`m-4`, `mx-2`, `mt-6 mb-4`, etc.)
32
32
 
33
- > **💡 EXPLICIT SIZE VS NATIVE UNDEFINED**
34
- > In Titanium, components like `Label`, `Button`, and `Switch` default to `SIZE` behavior, but their property values are `undefined`. This applies to **any component whose default size is `Ti.UI.SIZE`**, not just `Label` and `Button`.
35
- >
36
- > According to the **UI Composite Layout Behavior Spec**, if a dimension is `undefined` and you apply two opposite pins (e.g., `left` and `right` via `mx-*`, or `top` and `bottom` via `my-*`), the motor will **compute the dimension based on the pins**, causing the element to stretch.
37
- >
38
- > **PurgeTSS** recommends adding an explicit `SIZE` reset on the affected axis:
39
- >
40
- > - Use `h-auto` when `mt-*` + `mb-*` or `my-*` would otherwise stretch the height.
41
- > - Use `w-auto` when `ml-*` + `mr-*` or `mx-*` would otherwise stretch the width.
42
- > - Use `wh-auto` as the safe reset when both axes may be affected.
33
+ > **See also**: the "Explicit SIZE vs native undefined" callout under [Community-Discovered Patterns](#community-discovered-patterns) explains when to use `h-auto`, `w-auto`, and `wh-auto` to prevent unwanted stretching.
43
34
 
44
35
  ### Practical Examples
45
36
 
@@ -273,7 +264,7 @@ This generates classes for ALL color properties:
273
264
  When PurgeTSS generates your `app.tss`, these resets appear first:
274
265
 
275
266
  ```tss
276
- /* PurgeTSS v7.2.7 */
267
+ /* PurgeTSS v7.6.0 */
277
268
  /* Created by César Estrada */
278
269
  /* https://purgetss.com */
279
270
 
@@ -291,6 +282,43 @@ When PurgeTSS generates your `app.tss`, these resets appear first:
291
282
 
292
283
  These are **always included** - they're fundamental to how PurgeTSS works.
293
284
 
285
+ ## Default font family classes (v7.5.3+)
286
+
287
+ Starting with PurgeTSS v7.5.3, three `fontFamily` utility classes are auto-generated even when `theme.fontFamily` is not defined in `config.cjs`. Each class maps to a different per-platform value so iOS and Android each pick a native system font family:
288
+
289
+ | Class | iOS | Android |
290
+ | ------------ | ---------------- | ------------ |
291
+ | `font-sans` | `Helvetica Neue` | `sans-serif` |
292
+ | `font-serif` | `Georgia` | `serif` |
293
+ | `font-mono` | `monospace` | `monospace` |
294
+
295
+ Usage:
296
+
297
+ ```xml
298
+ <Label class="font-sans text-lg text-gray-800" text="System sans font" />
299
+ <Label class="font-serif text-lg text-gray-800" text="System serif font" />
300
+ <Label class="font-mono text-sm text-gray-700" text="Monospaced code" />
301
+ ```
302
+
303
+ ### Override behavior
304
+
305
+ If you define `sans`, `serif`, or `mono` under `theme.fontFamily` (or `theme.extend.fontFamily`), your value replaces the default on **both** platforms — you no longer need a per-platform fork:
306
+
307
+ ```javascript
308
+ // purgetss/config.cjs
309
+ module.exports = {
310
+ theme: {
311
+ extend: {
312
+ fontFamily: {
313
+ sans: 'Inter-Regular' // replaces Helvetica Neue / sans-serif on both platforms
314
+ }
315
+ }
316
+ }
317
+ };
318
+ ```
319
+
320
+ This keeps `font-serif` and `font-mono` at their defaults while `font-sans` now resolves to `Inter-Regular` on iOS and Android.
321
+
294
322
  ## Overriding Resets
295
323
 
296
324
  If you need different default behavior, add custom rules to `_app.tss` or `config.cjs`:
@@ -304,12 +332,12 @@ If you need different default behavior, add custom rules to `_app.tss` or `confi
304
332
  }
305
333
  ```
306
334
 
307
- **In `config.cjs` (preferred):**
335
+ **In `config.cjs` (preferred, v7.5.0+):**
308
336
  ```javascript
309
337
  module.exports = {
310
338
  theme: {
311
- View: {
312
- DEFAULT: {
339
+ extend: {
340
+ View: {
313
341
  width: Ti.UI.FILL,
314
342
  height: Ti.UI.SIZE
315
343
  }
@@ -318,8 +346,11 @@ module.exports = {
318
346
  }
319
347
  ```
320
348
 
349
+ > **⚠️ DEPRECATED SHAPE (pre-v7.5.0)**
350
+ > The older `theme.View.DEFAULT` shape is deprecated as of PurgeTSS v7.5.0. Migrate to `theme.extend.View` shown above. The same extend-based shape works for any Ti element default (`Window`, `Label`, `ImageView`, etc.).
351
+
321
352
  > **💡 TIP**
322
- > Prefer `config.cjs` for custom defaults because they're preserved when PurgeTSS regenerates `app.tss`.
353
+ > Prefer `config.cjs` for custom defaults because they're preserved when PurgeTSS regenerates `app.tss`. For advanced overrides — including per-platform defaults, `apply` shorthand, and pseudo-class targeting — see `customization-deep-dive.md`.
323
354
 
324
355
  ## Summary Table
325
356
 
@@ -10,6 +10,16 @@ This guide collects practical UI patterns that stay aligned with Titanium layout
10
10
  - Prefer `w-screen` over `w-full` when you need `Ti.UI.FILL`.
11
11
  - For dynamic components, prefer `$.UI.create()` over manual `Ti.UI.create*()` styling.
12
12
 
13
+ ## Related References
14
+
15
+ - **iOS Large Titles**: For windows inside a `NavigationWindow` or `TabGroup` that use iOS large titles, pair `content-w-screen` + `content-h-auto` on the `ScrollView` with `extendEdges`, `autoAdjustScrollViewInsets`, and `largeTitleEnabled` on the `Window`. See `ios-large-titles.md` for the full three-property pattern and `config.cjs` global defaults.
16
+ - **Light / Dark mode**: For automatic theme switching, see `appearance-module.md` (runtime appearance detection) and `semantic-colors.md` (adaptive color tokens). These pair with PurgeTSS utility classes to produce UIs that respect system appearance.
17
+ - **Customizing resets and defaults**: See `titanium-resets.md` and `customization-deep-dive.md` for overriding the `View`, `Window`, and `ImageView` defaults via `theme.extend.View`.
18
+
19
+ ## Community-Discovered Patterns
20
+
21
+ The catalog below collects real-world UI patterns verified against Titanium's composite/horizontal/vertical layout behavior and PurgeTSS's utility classes. These are not copied from the upstream docs — they are curated, production-tested combinations.
22
+
13
23
  ## Cards
14
24
 
15
25
  ### Elevated Card
@@ -156,11 +166,24 @@ This guide collects practical UI patterns that stay aligned with Titanium layout
156
166
 
157
167
  ### Icon Button
158
168
 
169
+ In Titanium, a `Button` with an icon typically uses a single icon-font `Button` (no adjacent `Label`). Set the icon font on the button and use the glyph as the `title`:
170
+
159
171
  ```xml
160
- <Button class="rounded-lg bg-blue-500 text-white" title="Save" />
161
- <Label class="fas fa-save ml-2 text-white" />
172
+ <!-- Icon-only button using Font Awesome solid -->
173
+ <Button class="fas rounded-lg bg-blue-500 text-white" title="&#xf0c7;" />
162
174
  ```
163
175
 
176
+ For an icon-with-text pair, use a horizontal `View` containing an icon `Label` and a text `Label`, then bind a `touchstart`/`click` handler on the container:
177
+
178
+ ```xml
179
+ <View onClick="doSave" class="horizontal rounded-lg bg-blue-500">
180
+ <Label class="fas ml-4 my-3 text-white" text="&#xf0c7;" />
181
+ <Label class="mx-3 my-3 text-base font-semibold text-white" text="Save" />
182
+ </View>
183
+ ```
184
+
185
+ > **Note**: The Font Awesome `fas` class only applies the font family. Glyphs are passed as Unicode in `title`/`text`. See the PurgeTSS Font Awesome integration docs for the full glyph map.
186
+
164
187
  ## Navigation Patterns
165
188
 
166
189
  ### Top Bar with Title and Action
@@ -247,15 +270,18 @@ This guide collects practical UI patterns that stay aligned with Titanium layout
247
270
  <Label class="left-4 center text-xl font-bold text-gray-800" text="Screen Title" />
248
271
  </View>
249
272
 
250
- <ScrollView class="h-screen w-screen">
251
- <View class="vertical">
252
- <Label class="mx-4 mt-4 text-base text-gray-700" text="Main content goes here." />
253
- </View>
273
+ <ScrollView class="vertical content-w-screen content-h-auto">
274
+ <Label class="mx-4 mt-4 text-base text-gray-700" text="Main content goes here." />
254
275
  </ScrollView>
255
276
  </View>
256
277
  </Window>
257
278
  ```
258
279
 
280
+ > **ScrollView sizing (PurgeTSS v7.3+)**
281
+ > The recommended pattern for a vertically scrolling `ScrollView` is `class="vertical content-w-screen content-h-auto"`. This sets `layout=vertical`, `contentWidth=Ti.UI.FILL`, and `contentHeight=Ti.UI.SIZE` so the content region grows to fit its children. Older examples using `h-screen w-screen` on the `ScrollView` itself describe the viewport, not the content — use the `content-*` variants to describe how the scrollable content flows.
282
+ >
283
+ > For iOS windows with large titles, also apply `extendEdges`, `autoAdjustScrollViewInsets`, and `largeTitleEnabled` on the `Window`. See `ios-large-titles.md`.
284
+
259
285
  ### Two-Column Tablet Layout
260
286
 
261
287
  ```xml