@maccesar/titools 2.9.0 → 2.10.1

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.
@@ -2,15 +2,71 @@
2
2
 
3
3
  The `Appearance` export from `purgetss.ui` (added in **v7.5.3**) handles Light / Dark / System mode switching and persists the user's choice across app restarts. It is a thin, deterministic wrapper around Titanium's native `Ti.UI.overrideUserInterfaceStyle` plus `Ti.App.Properties`, exposed as a singleton so every controller in the app reads and writes the same state.
4
4
 
5
- This file covers the four public methods, the required setup, a full Settings-view example, and the lifecycle that keeps the saved mode in sync with the UI. For the color wiring that actually makes the UI *respond* to mode changes, see [semantic-colors.md](./semantic-colors.md).
5
+ This reference follows the same workflow-first / API-second split the official PurgeTSS documentation now uses:
6
+
7
+ - **Workflow** — initialize at startup, then build a Settings view that calls `Appearance.set(...)` (mirrors `best-practices/1-appearance-setup.md`).
8
+ - **API reference** — the four public methods, exact signatures, and lifecycle (mirrors `purgetss-ui/10-appearance.md`).
9
+
10
+ For the color wiring that actually makes the UI *respond* to mode changes, see [semantic-colors.md](./semantic-colors.md).
6
11
 
7
12
  > **INFO**
8
13
  >
9
14
  > Requires PurgeTSS **>= 7.5.3**. On older versions the `Appearance` export does not exist — the `require('purgetss.ui')` call will succeed, but `Appearance` will be `undefined`. Run `purgetss --version` in the project root to confirm.
10
15
 
11
- ## Setup
16
+ ## How it fits together
17
+
18
+ The Appearance module sits between three moving parts: a JS singleton, the OS-level interface style, and the semantic colors generated by PurgeTSS. The diagram below shows how a single `Appearance.init()` call at startup wires them together, and what happens when the user later picks a mode in Settings.
19
+
20
+ ```
21
+ ┌─ app/lib/appearance.js (singleton from purgetss.ui) ─────────────────┐
22
+ │ │
23
+ │ Appearance.init() ────► reads 'userInterfaceStyle' │
24
+ │ from Ti.App.Properties │
25
+ │ applies Ti.UI.overrideUserInterfaceStyle │
26
+ │ │
27
+ │ Appearance.set(m) ────► writes Ti.App.Properties │
28
+ │ applies Ti.UI.overrideUserInterfaceStyle │
29
+ │ │
30
+ │ Appearance.get() ────► returns 'system' | 'light' | 'dark' │
31
+ │ Appearance.toggle()────► flips between 'light' and 'dark' │
32
+ │ │
33
+ └──────────────────────────────────────────────────────────────────────┘
34
+
35
+
36
+ ┌─ Ti.UI.semanticColorType (runtime) ──────────────────────────────────┐
37
+ │ │
38
+ │ Light mode ─► resolves bg-surface to surfaceColor.light │
39
+ │ resolves text-on-surface to textColor.light │
40
+ │ │
41
+ │ Dark mode ─► resolves bg-surface to surfaceColor.dark │
42
+ │ resolves text-on-surface to textColor.dark │
43
+ │ │
44
+ └──────────────────────────────────────────────────────────────────────┘
45
+
46
+
47
+ ┌─ Utility classes in views ───────────────────────────────────────────┐
48
+ │ │
49
+ │ bg-surface, text-on-surface, bg-border, bg-surface-high, ... │
50
+ │ Repaint instantly on every Appearance.set(...) call. │
51
+ │ │
52
+ └──────────────────────────────────────────────────────────────────────┘
53
+ ```
54
+
55
+ `Ti.App.Properties` is the persistence layer — the value written during `set(...)` is what `init()` reads on the next launch. Clearing app storage or reinstalling resets to `'system'`.
56
+
57
+ ## Workflow
58
+
59
+ The fastest path from zero to a working Light / Dark toggle is three steps. Each one maps to one section of the official `best-practices/1-appearance-setup.md` guide.
12
60
 
13
- Call `Appearance.init()` **once** at app startup, before opening the first window. The standard place is the top of `app/controllers/index.js`.
61
+ ### 1. Define semantic colors
62
+
63
+ Create `app/assets/semantic.colors.json` and register the color names under `theme.extend.colors` in `purgetss/config.cjs`. The class names you use in XML (`bg-surface`, `text-on-surface`, `bg-border`, ...) come from this mapping.
64
+
65
+ See [semantic-colors.md](./semantic-colors.md) for the JSON schema, the `config.cjs` mapping, nesting rules, and alpha transparency.
66
+
67
+ ### 2. Initialize Appearance at startup
68
+
69
+ Call `Appearance.init()` **once** before opening the first window. The standard place is the top of `app/controllers/index.js`:
14
70
 
15
71
  `app/controllers/index.js`
16
72
  ```js
@@ -27,22 +83,11 @@ $.navWin.open()
27
83
  >
28
84
  > Call `Appearance.init()` **before** `$.navWin.open()` (or whichever window you open first). If the window opens first, it will render once against the system default and then flicker to the saved mode when `init()` runs — a visible flash on cold launch.
29
85
 
30
- ## Methods
31
-
32
- | Method | Description |
33
- | ------------ | --------------------------------------------------------------------------- |
34
- | `init()` | Restore the saved mode from `Ti.App.Properties` and apply it via `Ti.UI.overrideUserInterfaceStyle`. Call once at app startup before opening the first window. |
35
- | `set(mode)` | Apply and persist a mode. Accepts `'system'`, `'light'`, or `'dark'`. Any other value is silently ignored. |
36
- | `get()` | Returns the current mode as a string: `'system'`, `'light'`, or `'dark'`. |
37
- | `toggle()` | Switch between `'light'` and `'dark'`. Skips `'system'` — if current mode is `'system'` the next call produces `'dark'`. |
38
-
39
- All four methods are **synchronous**. They do not fire events; if you need to react to a mode change, call your update logic right after `Appearance.set(...)`.
40
-
41
- ## Full Settings-view example
86
+ ### 3. Build an Appearance toggle
42
87
 
43
- A three-row settings panel — System / Light / Dark — where each row shows a `fa-solid fa-circle-check` icon next to the currently active mode. Tapping a row calls `Appearance.set(...)` and updates the check icons.
88
+ Build a Settings view where users pick `System`, `Light`, or `Dark`. Tapping a row calls `Appearance.set(...)` and updates the check icons.
44
89
 
45
- ### View
90
+ #### View
46
91
 
47
92
  `app/views/settings.xml`
48
93
  ```xml
@@ -86,7 +131,7 @@ A three-row settings panel — System / Light / Dark — where each row shows a
86
131
  </Alloy>
87
132
  ```
88
133
 
89
- ### Controller
134
+ #### Controller
90
135
 
91
136
  `app/controllers/settings.js`
92
137
  ```js
@@ -95,16 +140,16 @@ const { Appearance } = require('purgetss.ui')
95
140
  // Sync check icons with the mode saved at app startup.
96
141
  updateUI(Appearance.get())
97
142
 
98
- const selectDark = () => selectAppearance('dark')
99
- const selectLight = () => selectAppearance('light')
100
- const selectSystem = () => selectAppearance('system')
143
+ function selectDark() { selectAppearance('dark') }
144
+ function selectLight() { selectAppearance('light') }
145
+ function selectSystem() { selectAppearance('system') }
101
146
 
102
- const selectAppearance = (value) => {
147
+ function selectAppearance(value) {
103
148
  Appearance.set(value)
104
149
  updateUI(value)
105
150
  }
106
151
 
107
- const updateUI = (value) => {
152
+ function updateUI(value) {
108
153
  $.themeDarkCheck.visible = (value === 'dark')
109
154
  $.themeLightCheck.visible = (value === 'light')
110
155
  $.themeSystemCheck.visible = (value === 'system')
@@ -113,9 +158,24 @@ const updateUI = (value) => {
113
158
 
114
159
  The background, text, border, and icon colors in the XML come from semantic color classes (`bg-surface`, `bg-surface-high`, `text-on-surface`, `bg-border`, etc.) — see [semantic-colors.md](./semantic-colors.md) for how to wire those up so the whole view flips when `Appearance.set('dark')` fires.
115
160
 
116
- ## Lifecycle
161
+ > **Why named functions in Alloy controllers?** Alloy resolves `onClick="selectDark"` by looking up a top-level identifier on the controller scope. Named function declarations (`function selectDark() { ... }`) hoist and are reliably resolvable; arrow functions assigned to `const` are not hoisted and tend to bite you when an XML attribute fires before the binding has executed. The official setup guide uses named functions for this reason.
117
162
 
118
- How the four moving parts — `Appearance.init()`, `Ti.App.Properties`, `Ti.UI.overrideUserInterfaceStyle`, and semantic colors — cooperate:
163
+ ## API reference
164
+
165
+ ### Methods
166
+
167
+ | Method | Description |
168
+ | ------------ | --------------------------------------------------------------------------- |
169
+ | `init()` | Restore the saved mode from `Ti.App.Properties` and apply it via `Ti.UI.overrideUserInterfaceStyle`. Call once at app startup before opening the first window. |
170
+ | `set(mode)` | Apply and persist a mode. Accepts `'system'`, `'light'`, or `'dark'`. Any other value is silently ignored. |
171
+ | `get()` | Returns the current mode as a string: `'system'`, `'light'`, or `'dark'`. |
172
+ | `toggle()` | Switch between `'light'` and `'dark'`. Skips `'system'` — if current mode is `'system'` the next call produces `'dark'`. |
173
+
174
+ All four methods are **synchronous**. They do not fire events; if you need to react to a mode change, call your update logic right after `Appearance.set(...)`.
175
+
176
+ ### Lifecycle
177
+
178
+ How the four moving parts — `Appearance.init()`, `Ti.App.Properties`, `Ti.UI.overrideUserInterfaceStyle`, and semantic colors — cooperate during a typical session:
119
179
 
120
180
  ```
121
181
  app startup
@@ -143,8 +203,6 @@ app startup
143
203
  +-- Preference survives the next cold launch
144
204
  ```
145
205
 
146
- `Ti.App.Properties` is the persistence layer — the value written during `set(...)` is what `init()` reads on the next launch. Clearing app storage or reinstalling resets to `'system'`.
147
-
148
206
  > **INFO**
149
207
  >
150
208
  > `toggle()` intentionally skips `'system'`. If the current mode is `'system'`, calling `toggle()` produces `'dark'`. If you want a three-way cycle (System -> Light -> Dark -> System), build it in userland with `Appearance.get()` + `Appearance.set(...)`; do not expect `toggle()` to do it.
@@ -254,6 +254,66 @@ Window: { default: { apply: 'exit-on-close-false bg-blue-500' } }
254
254
 
255
255
  Use the explicit `default:` wrapper when you also need platform blocks (`ios:`, `android:`) next to it. For the common case of one bundle of defaults, the shorthand reads better.
256
256
 
257
+ ### Extend mode vs replace mode
258
+
259
+ > **HEADLINE CHANGE — v7.9.0**
260
+ > This is the headline change of PurgeTSS v7.9.0. Before v7.9.0, `theme.Window` / `theme.View` / `theme.ImageView` still had the framework defaults merged in, which caused gradient ghosting and other surprising overrides. v7.9.0 makes top-level (non-`extend`) configs behave as true replace mode.
261
+ >
262
+ > If you previously used `theme.Window` (no `extend`) and depended on the white background or the iOS `hires: true` / `Ti.UI.SIZE` defaults still being there, you will need to either move that config under `theme.extend.Window` (extend mode) or add the previously-implicit utilities back into your `apply` string.
263
+
264
+ PurgeTSS follows the Tailwind convention for the three Ti Elements that ship with framework defaults — `Window`, `View`, and `ImageView`:
265
+
266
+ - **Extend mode** — `theme.extend.Window`, `theme.extend.View`, `theme.extend.ImageView`. Your customization **merges** with the framework defaults. The white Window `backgroundColor`, `Ti.UI.SIZE` width/height on `View`, and iOS `hires: true` on `ImageView` stay in place unless you override them with `apply`. Use this when you want to add to the defaults, not replace them.
267
+ - **Replace mode** — `theme.Window`, `theme.View`, `theme.ImageView` (top level, no `extend`). Your config **replaces** the framework defaults entirely. The white Window background is omitted, the `Ti.UI.SIZE` width/height on `View` is omitted, and the iOS `hires: true` on `ImageView` is omitted. Your `apply` becomes the source of truth for that Ti Element.
268
+
269
+ Use replace mode when you want full control and do not want a preset mixed in. The canonical case is a Window declared at `theme.Window` with a `backgroundGradient`, where the previously-merged white `backgroundColor` would render on top of the gradient and produce a "ghost" white wash.
270
+
271
+ `./purgetss/config.cjs - replace mode (v7.9.0+)`
272
+ ```javascript
273
+ module.exports = {
274
+ theme: {
275
+ Window: {
276
+ apply: 'bg-gradient-to-b from-blue-500 to-purple-600'
277
+ }
278
+ }
279
+ };
280
+ ```
281
+
282
+ `./purgetss/styles/utilities.tss`
283
+ ```tss
284
+ 'Window': { backgroundGradient: { type: 'linear', colors: [...], startPoint: ..., endPoint: ... } }
285
+ ```
286
+
287
+ Note the missing `backgroundColor: '#FFFFFF'`. Replace mode skipped the framework default, so the gradient renders cleanly. Before v7.9.0 the same config produced:
288
+
289
+ ```tss
290
+ /* Pre-v7.9.0 — ghost white background covering the gradient */
291
+ 'Window': { backgroundColor: '#FFFFFF', backgroundGradient: { type: 'linear', colors: [...] } }
292
+ ```
293
+
294
+ If you wanted the framework white background **and** something else on top, that is what extend mode is for:
295
+
296
+ `./purgetss/config.cjs - extend mode`
297
+ ```javascript
298
+ module.exports = {
299
+ theme: {
300
+ extend: {
301
+ Window: {
302
+ apply: 'exit-on-close-false'
303
+ }
304
+ }
305
+ }
306
+ };
307
+ ```
308
+
309
+ `./purgetss/styles/utilities.tss`
310
+ ```tss
311
+ /* Framework default backgroundColor is preserved, exitOnClose merged in */
312
+ 'Window': { backgroundColor: '#FFFFFF', exitOnClose: false }
313
+ ```
314
+
315
+ The same extend-vs-replace distinction applies to `View` (`Ti.UI.SIZE` width/height defaults) and `ImageView` (iOS `hires: true` default).
316
+
257
317
  ### Apply Wins Over Static Defaults
258
318
 
259
319
  If `apply` sets a property that the component already has as a built-in default, the applied value replaces the original instead of both ending up in the final TSS:
@@ -7,6 +7,47 @@ When you need a one-off value that is not in the defaults, use arbitrary values
7
7
  >
8
8
  > You cannot use square bracket notation like in Tailwind because Titanium handles platform and conditional statements in `.tss` files differently.
9
9
 
10
+ ## Class syntax pre-validation
11
+
12
+ Starting with PurgeTSS v7.8.0, the build runs a pre-validation pass over class names found in XML views and JS controllers before purging. When it spots a malformed arbitrary-value utility, it stops and prints a structured `Class Syntax Error` block so the offending class can be fixed before any TSS is generated. If multiple errors are present, all of them are reported in the same run.
13
+
14
+ Each error block includes:
15
+
16
+ - The file path where the offending class lives
17
+ - The line number inside that file
18
+ - The offending class name (as authored)
19
+ - A `Fix:` suggestion with the corrected class name
20
+
21
+ Example shape:
22
+
23
+ ```
24
+ Class Syntax Error
25
+ File: app/views/index.xml
26
+ Line: 12
27
+ Found: top-(-10)
28
+ Fix: -top-(10)
29
+ ```
30
+
31
+ ### Detected patterns
32
+
33
+ The validator catches five narrow, actionable mistakes:
34
+
35
+ | Pattern | Offending input | Suggested fix | Notes |
36
+ | ----------------------------- | --------------- | ------------- | ---------------------------------------------------------------- |
37
+ | Inverted negative sign | `top-(-10)` | `-top-(10)` | The `-` prefix goes before the rule, not inside the value |
38
+ | Tailwind-style brackets | `top-[10px]` | `top-(10px)` | PurgeTSS uses parentheses, not square brackets, for arbitrary values |
39
+ | Empty parentheses | `wh-()` | (flagged, no auto-fix) | Add a value such as `wh-(10)` |
40
+ | Whitespace inside parentheses | `wh-( 200 )` | `wh-(200)` | No spaces allowed between `(`, the value, and `)` |
41
+ | Redundant `px` unit | `top-(10px)` | `top-(10)` | PurgeTSS treats unit-less arbitrary values as pixels |
42
+
43
+ ### Unknown classes are still silently dropped
44
+
45
+ The pre-validator only fires on the five patterns above. Any other unknown class — typos, custom utilities not yet declared, vendor classes that are not enabled in `config.cjs` — is **not** reported as a `Class Syntax Error`. Those classes continue to flow into the `// Unused or unsupported classes` comment block in `app.tss`, exactly like before. This keeps the validator focused on actionable mistakes and avoids noise while you sketch out class names.
46
+
47
+ ### v7.8.0 parser fix for negatives inside parentheses
48
+
49
+ Before v7.8.0, an inverted negative such as `top-(-10)` could be silently misparsed by the arbitrary-value pipeline (the `-` inside the parentheses confused the token matcher, producing wrong or missing TSS output without any warning). v7.8.0 hardens that path: the parser now correctly recognizes `top-(-10)` as authored, classifies it as an inverted-negative-sign error, and surfaces the `Class Syntax Error` block with the `-top-(10)` fix instead of producing silent garbage.
50
+
10
51
  ## Color Properties
11
52
 
12
53
  You can set arbitrary color values for all available color properties using `hex`, `rgb`, or `rgba` values, directly in XML files or in `config.cjs`.
@@ -1,6 +1,17 @@
1
1
  # PurgeTSS CLI Commands
2
2
 
3
- > **Info: What's new in v7.5.3 / v7.6.0**
3
+ > **Info: What's new in v7.5.3 / v7.6.x / v7.7.0 / v7.8.0 / v7.9.0**
4
+ > - **Opacity modifiers on semantic colors (v7.9.0)** — writing `bg-surface/65` (or any opacity modifier on a class mapped to a name in `semantic.colors.json`) now produces a working rule with Light/Dark switching preserved. PurgeTSS auto-derives a `<originalKey>_<alphaPercent>` entry in `semantic.colors.json` with the original `light`/`dark` hex values plus the requested alpha for both modes, then emits the rule against the derived key. Re-runs are idempotent; manual edits with conflicting values halt the build with a `Conflict` error. New alpha entries require one full Titanium build to be picked up — Liveview hot-reload alone does not refresh `semantic.colors.json`.
5
+ > - **Gradient + Window/View/ImageView preset fixes (v7.9.0)** — `theme.Window` / `theme.View` / `theme.ImageView` no longer leak the framework presets (white background, `Ti.UI.SIZE`, iOS `hires: true`) when defined at the top level (replace mode), so a Window declared at `theme.Window` with a `backgroundGradient` no longer ghosts on top of a default `backgroundColor: '#FFFFFF'`. `theme.extend.Window` keeps merging with the defaults as before. Also fixed: tonal palette inversion bug, position-dependent `from`/`to` color ordering after `sort()`, and `bg-gradient-to-X` direction silently dropped when combined with `from-X to-Y` colors.
6
+ > - **Glossary path renamed (v7.9.0, breaking)** — the user-facing glossary output path was renamed from `purgetss/experimental/tailwind-classes/` to `purgetss/glossary/tailwind-classes/`. Tooling or CI that reads from the old path needs updating on upgrade — no transition shim. The `--glossary` flag and command surface are unchanged.
7
+ > - **`images --width <n>` flag (v7.8.0)** — pins Android `mdpi` (= iPhone `@1x`) to a specific pixel width, with larger scales deriving as ×1.5, ×2, ×3, ×4 from that base. Use it for SVG sources from vector editors with disproportionate viewBoxes (Affinity, Illustrator). CLI-only; there is no matching `images:` config property because the right width is per-asset. See [`images` Command](#images-command).
8
+ > - **Class syntax pre-validation (v7.8.0)** — `purgetss` now stops with a structured `Class Syntax Error` block (file + line + suggested fix) when it detects known class-name mistakes: inverted negative sign (`top-(-10)` → `-top-(10)`), Tailwind-style brackets (`top-[10px]` → `top-(10px)`), empty parentheses (`wh-()`), whitespace inside parentheses (`wh-( 200 )`), and redundant `px` unit (`top-(10px)` → `top-(10)`). All offenders are reported in one run. Generic unknown classes still flow into the `// Unused or unsupported classes` block in `app.tss`.
9
+ > - **Negative-values-in-parens parser fix (v7.8.0)** — classes like `top-(-10)`, `mt-(-5)`, and `origin-(-10,-20)` no longer crash with `Cannot read properties of null (reading 'pop')`. The parser now extracts the `(...)` portion first, so a `-` inside the value does not break the split.
10
+ > - **`brand:` config grouped (v7.7.0)** — the flat `brand:` block from v7.6.0 was reorganized into purpose-based sections: `brand.logos`, `brand.padding`, `brand.android`, `brand.ios`, and `brand.colors`. Old projects keep working — newly-generated configs use the grouped form.
11
+ > - **Separate Android brand inputs (v7.7.0)** — `brand` can now use one logo for the general brand set, another for Android launcher icons (`logos.androidLauncher` / `--icon-logo`), and another for Android 12+ splash artwork (`logos.androidSplash` / `--splash-logo`). Drop `logo-icon.*` and `logo-splash.*` into `purgetss/brand/` or set the paths in `config.cjs`.
12
+ > - **Legacy Android splash fallback (v7.7.0)** — `purgetss brand` now regenerates `app/assets/android/default.png` (Alloy) or `Resources/android/default.png` (Classic). `cleanup-legacy` no longer removes `default.png`.
13
+ > - **`semantic` works in Classic projects (v7.6.2)** — writes to `Resources/semantic.colors.json` for Classic, keeps writing to `app/assets/semantic.colors.json` for Alloy.
14
+ > - **Confirmation prompt for destructive writes (v7.6.1)** — `brand` and `images` ask `[y/N/a]` before overwriting (auto-skipped on non-TTY, with `-y`/`--yes`, or `PURGETSS_YES=1`).
4
15
  > - **New `brand` command (v7.6.0)** — generates the complete Titanium branding set (launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, optional notification/splash) from a single SVG or PNG logo. See [`brand` Command](#brand-command) and the deep-dive [app-branding.md](./app-branding.md).
5
16
  > - **New `images` command (v7.6.0)** — generates multi-density UI images (Android `res-*` densities + iPhone `@1x`/`@2x`/`@3x` scales) from sources in `./purgetss/images/`. See [`images` Command](#images-command) and the deep-dive [multi-density-images.md](./multi-density-images.md).
6
17
  > - **New `semantic` command (v7.6.0)** — generates Titanium semantic colors (Light/Dark) into `app/assets/semantic.colors.json` with two modes (tonal palette vs. single purpose-based color). See [`semantic` Command](#semantic-command) and the deep-dive [semantic-colors.md](./semantic-colors.md).
@@ -68,12 +79,32 @@ module.exports = {
68
79
  }
69
80
  },
70
81
  brand: {
71
- splash: false, // also generate splash_icon.png × 5
72
- padding: '15%', // Android safe-zone. Range: 12% tight (mature logos) 20% conservative. Spec floor 19.44%.
73
- iosPadding: '4%', // iOS aesthetic. Range: 2% bold — 8% conservative. No launcher mask.
74
- darkBgColor: null, // opaque dark bg for DefaultIcon-Dark.png (null = transparent per Apple HIG)
75
- bgColor: '#FFFFFF', // Android adaptive bg + iOS/marketplace flatten
76
- notification: false, // also generate ic_stat_notify.png × 5
82
+ logos: {
83
+ // Optional overrides. If omitted, PurgeTSS auto-discovers files from purgetss/brand/:
84
+ // primary: './docs/logo.svg',
85
+ // androidLauncher: './docs/app-icon.svg',
86
+ // androidSplash: './docs/splash.svg',
87
+ // monochrome: './docs/logo-mono.svg',
88
+ // iosDark: './docs/logo-dark.svg',
89
+ // iosTinted: './docs/logo-tinted.svg'
90
+ },
91
+ padding: {
92
+ ios: '4%', // iOS aesthetic padding. Range 2-8%.
93
+ androidLegacy: '10%', // legacy ic_launcher.png padding %
94
+ androidAdaptive: '19%' // adaptive icon safe-zone %. Spec floor 19.44%.
95
+ },
96
+ android: {
97
+ splash: false, // also generate splash_icon.png × 5
98
+ notification: false // also generate ic_stat_notify.png × 5
99
+ },
100
+ ios: {
101
+ dark: true, // emit DefaultIcon-Dark.png (set false to skip)
102
+ tinted: true, // emit DefaultIcon-Tinted.png (set false to skip)
103
+ darkBackground: null // null = transparent per Apple HIG; set a hex like '#111111' for an opaque dark bg
104
+ },
105
+ colors: {
106
+ background: '#FFFFFF' // Android adaptive bg + iOS/marketplace flatten
107
+ },
77
108
  confirmOverwrites: true // prompt before overwriting files (set false to skip)
78
109
  },
79
110
  images: {
@@ -164,7 +195,7 @@ Recommended VSCode extensions:
164
195
 
165
196
  ## `brand` Command
166
197
 
167
- Introduced in v7.6.0. Generates the complete Titanium branding set (launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, optional notification/splash icons) from a single logo image. Alloy and Classic projects are auto-detected.
198
+ Introduced in v7.6.0, restructured in v7.7.0. Generates the complete Titanium branding set (launcher icons, adaptive icons, iOS 18+ Dark/Tinted variants, marketplace artwork, optional notification/splash icons) from a single logo image — with optional Android-specific logo overrides when you need them. Alloy and Classic projects are auto-detected.
168
199
 
169
200
  > **Tip**
170
201
  > This is a quick reference. See [app-branding.md](./app-branding.md) for the complete guide — workflow, padding guidance, Android dark mode, iOS 18+ variants, alpha channel handling, and troubleshooting.
@@ -176,25 +207,56 @@ purgetss brand path/to/logo.svg # positional logo path override
176
207
 
177
208
  ### Flags
178
209
 
210
+ **Project & output**
211
+
179
212
  | Flag | Purpose |
180
213
  | --- | --- |
181
214
  | `--project <path>` | Project root (defaults to cwd). |
182
215
  | `--dry-run` | Preview what would be generated without writing any files. |
183
216
  | `--output <dir>` | Stage into `<dir>` instead of writing in place. |
184
217
  | `-y, --yes` | Skip the overwrite confirmation prompt for this invocation. |
218
+
219
+ **Visual customization**
220
+
221
+ | Flag | Purpose |
222
+ | --- | --- |
185
223
  | `--bg-color <hex>` | Background color for Android adaptive + iOS/marketplace flatten. |
186
- | `--padding <n>` | Android safe-zone percentage (range `12-20`, default `15`). |
187
- | `--ios-padding <n>` | iOS aesthetic padding percentage (range `2-8`, default `4`). |
224
+ | `--padding <n>` | Shortcut: sets BOTH Android paddings to the same value for one run. |
225
+ | `--android-adaptive-padding <n>` | Adaptive icon safe-zone % (default `19`). |
226
+ | `--android-legacy-padding <n>` | Legacy `ic_launcher.png` padding % (default `10`). |
227
+ | `--ios-padding <n>` | iOS aesthetic padding % (range `2-8`, default `4`). |
228
+
229
+ **Optional asset types**
230
+
231
+ | Flag | Purpose |
232
+ | --- | --- |
188
233
  | `--notification` | Also emit `ic_stat_notify.png × 5`. |
189
234
  | `--splash` | Also emit `splash_icon.png × 5`. |
235
+
236
+ **Logo variants & overrides**
237
+
238
+ | Flag | Purpose |
239
+ | --- | --- |
240
+ | `--icon-logo <path>` | Override `purgetss/brand/logo-icon.{svg,png}` for Android launcher icons (square mark for non-square main logos). |
190
241
  | `--monochrome-logo <path>` | Override `purgetss/brand/logo-mono.{svg,png}`. |
191
242
  | `--dark-logo <path>` | Override `purgetss/brand/logo-dark.{svg,png}`. |
192
243
  | `--dark-bg-color <hex>` | Opaque dark bg for `DefaultIcon-Dark.png` (default: transparent per Apple HIG). |
244
+ | `--splash-logo <path>` | Override `purgetss/brand/logo-splash.{svg,png}` for Android 12+ splash artwork. |
193
245
  | `--tinted-logo <path>` | Override `purgetss/brand/logo-tinted.{svg,png}`. |
194
246
  | `--no-dark` | Skip `DefaultIcon-Dark.png`. |
195
247
  | `--no-tinted` | Skip `DefaultIcon-Tinted.png`. |
196
- | `--cleanup-legacy` | Remove obsolete branding artifacts (reads `tiapp.xml` for safety rules). |
248
+
249
+ **Legacy cleanup**
250
+
251
+ | Flag | Purpose |
252
+ | --- | --- |
253
+ | `--cleanup-legacy` | Remove obsolete branding artifacts (reads `tiapp.xml` for safety rules). Keeps `default.png` on purpose. |
197
254
  | `--aggressive` | With `--cleanup-legacy`, also remove `ldpi` density folders. |
255
+
256
+ **Diagnostics**
257
+
258
+ | Flag | Purpose |
259
+ | --- | --- |
198
260
  | `--notes` | Print full `tiapp.xml` snippets + padding tuning guide. |
199
261
  | `--debug` | Print extra diagnostics. |
200
262
 
@@ -202,22 +264,44 @@ purgetss brand path/to/logo.svg # positional logo path override
202
264
 
203
265
  - `[logo-path]` (optional) — overrides auto-discovery of `purgetss/brand/logo.{svg,png}`.
204
266
 
205
- ### Config block
267
+ ### Config block (v7.7.0 grouped structure)
206
268
 
207
269
  Defaults live under `brand:` in `purgetss/config.cjs` and are injected automatically:
208
270
 
209
271
  ```javascript
210
272
  brand: {
211
- splash: false, // also generate splash_icon.png × 5
212
- padding: '15%', // Android safe-zone. Range 12-20%.
213
- iosPadding: '4%', // iOS aesthetic padding. Range 2-8%.
214
- darkBgColor: null, // opaque dark bg for DefaultIcon-Dark.png (null = transparent per Apple HIG)
215
- bgColor: '#FFFFFF', // Android adaptive bg + iOS/marketplace flatten
216
- notification: false, // also generate ic_stat_notify.png × 5
273
+ logos: {
274
+ // Optional overrides. If omitted, PurgeTSS auto-discovers files from purgetss/brand/:
275
+ // primary: './docs/logo.svg',
276
+ // androidLauncher: './docs/app-icon.svg',
277
+ // androidSplash: './docs/splash.svg',
278
+ // monochrome: './docs/logo-mono.svg',
279
+ // iosDark: './docs/logo-dark.svg',
280
+ // iosTinted: './docs/logo-tinted.svg'
281
+ },
282
+ padding: {
283
+ ios: '4%', // iOS aesthetic padding. Range 2-8%.
284
+ androidLegacy: '10%', // legacy ic_launcher.png padding %
285
+ androidAdaptive: '19%' // adaptive icon safe-zone %. Spec floor 19.44%.
286
+ },
287
+ android: {
288
+ splash: false, // also generate splash_icon.png × 5
289
+ notification: false // also generate ic_stat_notify.png × 5
290
+ },
291
+ ios: {
292
+ dark: true, // emit DefaultIcon-Dark.png (set false to skip)
293
+ tinted: true, // emit DefaultIcon-Tinted.png (set false to skip)
294
+ darkBackground: null // null = transparent per Apple HIG; set a hex like '#111' for an opaque dark bg
295
+ },
296
+ colors: {
297
+ background: '#FFFFFF' // Android adaptive bg + iOS/marketplace flatten
298
+ },
217
299
  confirmOverwrites: true // prompt before overwriting files
218
300
  }
219
301
  ```
220
302
 
303
+ The recommended workflow is convention-first: drop files in `purgetss/brand/`, let auto-discovery pick them up. Treat `brand.logos.*` as optional overrides for one-off paths or when masters live outside `purgetss/brand/`.
304
+
221
305
  ### Confirmation prompt
222
306
 
223
307
  `brand` writes in place, so it asks `Continue? [y/N/a]` before overwriting anything. Choose `a` (always) to write `confirmOverwrites: false` into `config.cjs` and silence the prompt on future runs. The prompt is skipped automatically when `stdin` is not a TTY (`alloy.jmk` hook, CI, pipes), when `-y`/`--yes` is passed, or when `PURGETSS_YES=1` is set.
@@ -225,14 +309,24 @@ brand: {
225
309
  ### Examples
226
310
 
227
311
  ```bash
228
- purgetss brand # uses purgetss/brand/logo.svg + config
229
- purgetss brand --bg-color "#0B1326" # override bg color
230
- purgetss brand --notification --splash # add notification + splash
231
- purgetss brand --no-tinted # skip iOS 18+ tinted variant
232
- purgetss brand --dry-run # preview without writing
233
- purgetss brand --cleanup-legacy --dry-run # preview legacy cleanup
312
+ purgetss brand # uses purgetss/brand/logo.svg + config
313
+ purgetss brand --bg-color "#0B1326" # override bg color
314
+ purgetss brand --icon-logo ./docs/app-icon.svg # dedicated square Android launcher mark
315
+ purgetss brand --splash --splash-logo ./docs/splash.svg # custom Android 12+ splash artwork
316
+ purgetss brand --notification --splash # add notification + splash
317
+ purgetss brand --no-tinted # skip iOS 18+ tinted variant
318
+ purgetss brand --dry-run # preview without writing
319
+ purgetss brand --cleanup-legacy --dry-run # preview legacy cleanup
234
320
  ```
235
321
 
322
+ ### Android output groups
323
+
324
+ `brand` writes three Android-facing asset groups, each with a different job:
325
+
326
+ - `ic_launcher*` for the app icon and the default Android 12+ system splash path
327
+ - `splash_icon.png` when you pass `--splash` and want custom Android 12+ splash artwork
328
+ - `default.png` as the older Titanium Android splash fallback (regenerated since v7.7.0)
329
+
236
330
  ## `images` Command
237
331
 
238
332
  Introduced in v7.6.0. Generates multi-density variants of your UI images (buttons, illustrations, logos, screen graphics) from a single high-resolution source per image. Alloy and Classic projects are auto-detected.
@@ -254,6 +348,7 @@ purgetss images background/ # re-process one subfolder
254
348
  | `--ios` | Only emit iPhone scale variants. Mutually exclusive with `--android`. |
255
349
  | `--format <ext>` | Convert all outputs to `webp`, `jpeg`, `png`, `avif`, `gif`, or `tiff`. Default: keep source format. |
256
350
  | `--quality <n>` | Quality `0-100` for lossy formats. Default `85`. |
351
+ | `--width <n>` | (v7.8.0) Pin Android `mdpi` (= iPhone `@1x`) to `<n>` pixels wide. Larger scales derive as ×1.5, ×2, ×3, ×4 from that base; height stays proportional to the source's aspect ratio. Integer in `[1, 8192]`. |
257
352
  | `--dry-run` | Preview without writing any files. |
258
353
  | `--project <path>` | Project root (defaults to cwd). |
259
354
  | `-y, --yes` | Skip the overwrite confirmation prompt. |
@@ -263,6 +358,12 @@ purgetss images background/ # re-process one subfolder
263
358
 
264
359
  - `[source]` (optional) — path to override auto-discovery. Resolves first against `purgetss/images/` (short paths like `buttons/btn.png`), then against cwd.
265
360
 
361
+ ### When to use `--width`
362
+
363
+ Use `--width <n>` for SVG sources from vector editors with disproportionate viewBoxes — common in Affinity, Illustrator, and other design tools where the viewBox does not match the intended display size. Without the flag, every scale derives from the source's natural pixel size as a 4× master, which can produce unexpected output sizes.
364
+
365
+ When you pass an SVG without `--width`, the command prints a one-time hint and falls back to the legacy 4× behavior. This is CLI-only; there is no matching `images:` config property because the right width is per-asset.
366
+
266
367
  ### Config block
267
368
 
268
369
  Defaults live under `images:` in `purgetss/config.cjs`:
@@ -285,6 +386,7 @@ purgetss images background/pink-texture.png # re-process one file (sh
285
386
  purgetss images background/ # re-process one subfolder
286
387
  purgetss images --android # only Android densities
287
388
  purgetss images --format webp --quality 90 # convert all outputs to WebP
389
+ purgetss images logo.svg --width 256 # pin SVG output to 256 px @1x/mdpi
288
390
  purgetss images --dry-run # preview
289
391
  ```
290
392
 
@@ -322,6 +424,29 @@ purgetss semantic --single '#000000' overlayColor --alpha 50
322
424
 
323
425
  When `--dark` is omitted, it defaults to the light hex — useful for overlays/glass surfaces where alpha is the only variation.
324
426
 
427
+ ### Customizing the class name
428
+
429
+ The auto-mapping uses the most literal Titanium-style transform: strip `Color`, then kebab-case the rest (e.g. `surfaceColor` → `surface`, `textSecondaryColor` → `text-secondary`). If your design system prefers different names — for example `on-surface` instead of `text`, or nesting the surface family under `DEFAULT` / `high` — edit `config.cjs` after running the `--single` batch:
430
+
431
+ `./purgetss/config.cjs`
432
+ ```javascript
433
+ theme: {
434
+ extend: {
435
+ colors: {
436
+ surface: { DEFAULT: 'surfaceColor', high: 'surfaceHighColor' },
437
+ 'on-surface': 'textColor',
438
+ 'on-surface-variant': 'textSecondaryColor',
439
+ muted: 'textMutedColor',
440
+ border: 'borderColor',
441
+ accent: 'accentColor',
442
+ overlay: 'overlayColor'
443
+ }
444
+ }
445
+ }
446
+ ```
447
+
448
+ The next `purgetss build` picks up the renamed classes. Editing one generated mapping is faster than typing the whole structure from scratch. See [semantic-colors.md](./semantic-colors.md) for the full nested-vs-flat discussion (including the `[object Object]` pitfall when nesting without `DEFAULT`).
449
+
325
450
  ### Smart in-place updates
326
451
 
327
452
  If a `--single` name matches an existing palette shade — e.g. `purgetss semantic --single '#000' amazon500` while palette `amazon` exists — PurgeTSS narrows the operation to an in-place JSON value edit. The entry stays in its original position, and `config.cjs` is left untouched (the palette already maps to that key).
@@ -392,10 +517,10 @@ After copying the fonts, you can use them in Buttons and Labels. For example, fo
392
517
 
393
518
  ### Available Font Classes
394
519
 
395
- - [fontawesome.tss](https://github.com/macCesar/purgeTSS/blob/master/dist/fontawesome.tss)
396
- - [materialicons.tss](https://github.com/macCesar/purgeTSS/blob/master/dist/materialicons.tss)
397
- - [materialsymbols.tss](https://github.com/macCesar/purgeTSS/blob/master/dist/materialsymbols.tss)
398
- - [framework7icons.tss](https://github.com/macCesar/purgeTSS/blob/master/dist/framework7icons.tss)
520
+ - [fontawesome.tss](https://github.com/macCesar/purgeTSS/blob/main/dist/fontawesome.tss)
521
+ - [materialicons.tss](https://github.com/macCesar/purgeTSS/blob/main/dist/materialicons.tss)
522
+ - [materialsymbols.tss](https://github.com/macCesar/purgeTSS/blob/main/dist/materialsymbols.tss)
523
+ - [framework7icons.tss](https://github.com/macCesar/purgeTSS/blob/main/dist/framework7icons.tss)
399
524
 
400
525
  ### Copying Specific Font Vendors
401
526