@maccesar/titools 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,165 @@
1
+ # Migrating feedback surfaces
2
+
3
+ Migrate semantics, not proxy names. Replacing every native dialog with the same
4
+ custom modal preserves the original design problem.
5
+
6
+ ## Contents
7
+
8
+ - [Inventory](#inventory)
9
+ - [Classify before replacing](#classify-before-replacing)
10
+ - [Migrate one window at a time](#migrate-one-window-at-a-time)
11
+ - [Deliberate silence](#deliberate-silence)
12
+ - [Contract tests](#contract-tests)
13
+ - [Delivery verification](#delivery-verification)
14
+ - [When to share Widgets between apps](#when-to-share-widgets-between-apps)
15
+
16
+ ## Inventory
17
+
18
+ Search the application for:
19
+
20
+ ```bash
21
+ rg -n "createAlertDialog|createOptionDialog|createNotification|Toast|toast|snackbar|overlay" app/
22
+ ```
23
+
24
+ Also inspect helpers that wrap these calls, XML overlays, controller timers,
25
+ global events, and callbacks that can present UI indirectly. Record for every
26
+ occurrence:
27
+
28
+ - trigger and current message;
29
+ - success, information, warning, error, confirmation, or choice;
30
+ - blocking versus non-blocking;
31
+ - reversible versus irreversible;
32
+ - app-owned versus system-owned;
33
+ - callback side effects and cancellation behavior;
34
+ - owning window and cleanup path.
35
+
36
+ Do not implement the replacement until every occurrence has a semantic target
37
+ from `feedback-surfaces.md`.
38
+
39
+ ## Classify before replacing
40
+
41
+ Use these defaults:
42
+
43
+ - ordinary success and undoable actions → Snackbar;
44
+ - recoverable field validation → inline validation;
45
+ - persistent app condition → Banner or inline notice;
46
+ - blocking failure or required acknowledgement → Dialog;
47
+ - uncommon irreversible decision → destructive Dialog;
48
+ - several related actions or app-data choices → Bottom Sheet;
49
+ - long structured content → scrollable sheet or dedicated screen;
50
+ - OS permission, picker, share, date/time, biometric, or purchase flow → keep the
51
+ system surface.
52
+
53
+ Preserve domain behavior separately from presentation. Services return results
54
+ or errors; the window/controller that owns visible context chooses the surface.
55
+ Do not let a service create app-owned UI, especially before the root window
56
+ exists.
57
+
58
+ ## Migrate one window at a time
59
+
60
+ 1. Register the required Widget dependencies in `app/config.json`.
61
+ 2. Add the Widgets as the final visual children of the window XML.
62
+ 3. Attach modal Widgets after the window exists.
63
+ 4. Route each old result to its classified surface.
64
+ 5. Call Widget `destroy()` from the window close cleanup.
65
+ 6. Exercise success, failure, cancel, Back, backdrop, double tap, and consecutive
66
+ presentation paths.
67
+ 7. Remove the old dialog, local overlay, helper, timer, and listener after the
68
+ migrated window passes verification.
69
+ 8. Commit the completed vertical slice before moving to the next window.
70
+
71
+ Do not keep commented legacy implementations. Version control is the rollback
72
+ mechanism; commented duplicates hide incomplete migrations and stale callbacks.
73
+
74
+ ## Deliberate silence
75
+
76
+ Cancellation or an unknowable result can be a complete, intentional outcome.
77
+ Model it explicitly and do not infer a message from falsy values.
78
+
79
+ Example contract for an exported file:
80
+
81
+ ```javascript
82
+ function handleSaveResult(error, result) {
83
+ if (error) {
84
+ $.dialog.show({
85
+ title: L('error'),
86
+ message: error.message,
87
+ tone: 'error',
88
+ confirmTitle: L('close')
89
+ })
90
+ return
91
+ }
92
+
93
+ if (result.saved === true) {
94
+ $.snackbar.show({
95
+ message: L('file_saved'),
96
+ tone: 'success'
97
+ })
98
+ }
99
+
100
+ // saved === false: Android picker was cancelled; the internal original remains.
101
+ // saved === null: iOS does not report whether the person completed the save.
102
+ // Both outcomes are deliberately silent.
103
+ }
104
+ ```
105
+
106
+ Do not add "Cancelled" for `saved === false`. Do not claim success for
107
+ `saved === null`. Cover both branches with tests so a future fallback does not
108
+ erase the intended silence.
109
+
110
+ ## Contract tests
111
+
112
+ Add repository-appropriate static or unit tests for:
113
+
114
+ - all Widget folders, manifests, XML views, controllers, and dependency entries;
115
+ - each public method documented in `feedback-widget-contracts.md`;
116
+ - the expected Widget declared in every migrated window;
117
+ - absence of app-owned `Ti.UI.createAlertDialog` and
118
+ `Ti.UI.createOptionDialog` defaults;
119
+ - removal of the previous local Snackbar/overlay and timer;
120
+ - Snackbar replacement and timer reset;
121
+ - Dialog and Bottom Sheet FIFO ordering;
122
+ - exactly-once callbacks under double taps and competing close paths;
123
+ - Android Back and backdrop policy;
124
+ - cleanup of active and queued presentations;
125
+ - explicit silence for cancel and indeterminate save results.
126
+
127
+ Do not enforce blanket bans on native system surfaces. A remaining native call
128
+ must be tied to an OS-owned interaction or documented fallback.
129
+
130
+ ## Delivery verification
131
+
132
+ Run the project's real commands rather than assuming names. A complete migration
133
+ normally includes:
134
+
135
+ 1. lint with zero errors and no new warnings;
136
+ 2. unit and contract tests with no new failures;
137
+ 3. PurgeTSS generation when detected, followed by inspection of unused or
138
+ unsupported classes at the end of `app.tss`;
139
+ 4. Alloy compilation for Android and iOS;
140
+ 5. native builds for every supported platform;
141
+ 6. manual checks in light, dark, and system appearance;
142
+ 7. phone and tablet/large-screen checks;
143
+ 8. Back, backdrop, cancel, double tap, long list, and long localized text checks;
144
+ 9. VoiceOver and TalkBack focus/traversal checks;
145
+ 10. consecutive presentation and window-close cleanup checks.
146
+
147
+ Record pre-existing failures before migration and require the final run to add
148
+ none. Build artifacts are not proof of usable modal behavior; perform the manual
149
+ interaction checks on device or simulator.
150
+
151
+ ## When to share Widgets between apps
152
+
153
+ Extract or copy a Widget into another app only when:
154
+
155
+ - its API is stable and contains no business rules;
156
+ - localized strings and callbacks are supplied by the host;
157
+ - appearance depends on a documented semantic-token contract;
158
+ - platform and minimum Titanium/Alloy versions are declared;
159
+ - lifecycle and accessibility tests travel with it;
160
+ - at least two apps need the same behavior, or one app has a clear portability
161
+ requirement.
162
+
163
+ Do not extract merely because a view appeared three times. Extract because the
164
+ boundary is independently understandable, testable, and maintainable.
165
+
@@ -0,0 +1,140 @@
1
+ # Feedback surfaces and app-owned UI
2
+
3
+ Use this guide before choosing a Titanium proxy or drawing a component. The
4
+ surface follows the meaning of the event: style comes afterwards.
5
+
6
+ ## Contents
7
+
8
+ - [Decision sequence](#decision-sequence)
9
+ - [Feedback scale](#feedback-scale)
10
+ - [App-owned versus system-owned](#app-owned-versus-system-owned)
11
+ - [What to standardize next](#what-to-standardize-next)
12
+ - [Phone, tablet, and accessibility](#phone-tablet-and-accessibility)
13
+ - [References](#references)
14
+
15
+ ## Decision sequence
16
+
17
+ Classify the event in this order:
18
+
19
+ 1. **Owner**: does the app own the interaction, or does the operating system?
20
+ 2. **Blocking**: can the person continue safely without acknowledging it?
21
+ 3. **Recovery**: is the action reversible or is data at risk?
22
+ 4. **Location**: does the message belong to a field, a screen, or the whole app?
23
+ 5. **Persistence**: is it transient, persistent until state changes, or modal?
24
+ 6. **Choice shape**: is there one acknowledgement, a binary decision, or a list
25
+ of related actions?
26
+
27
+ Do not begin with "which native dialog can display this string?" That turns
28
+ different meanings into the same interruption and makes the app feel visually
29
+ inconsistent.
30
+
31
+ ## Feedback scale
32
+
33
+ | Situation | Preferred surface | Why |
34
+ | --- | --- | --- |
35
+ | Ordinary success or confirmation | Snackbar | Visible without interrupting the task |
36
+ | Undoable action | Snackbar with one Undo action | Keeps recovery close to the event |
37
+ | Non-blocking warning | Warning Snackbar | Calls attention without forcing acknowledgement |
38
+ | Invalid value in one field | Inline validation next to that field | Preserves context and identifies the fix |
39
+ | Form-wide recoverable validation | Inline summary; Snackbar only if no single field owns it | Avoids a modal loop |
40
+ | Persistent connectivity, sync, or account state | Banner or inline notice | Remains visible while the condition remains true |
41
+ | Blocking error or required acknowledgement | Dialog | The task cannot continue safely |
42
+ | Uncommon irreversible action | Destructive confirmation Dialog | Provides a safe escape before data loss |
43
+ | Several actions related to the current context | Bottom Sheet | Presents a compact choice without pretending it is an error |
44
+ | Long, searchable, or multi-select options | Bottom Sheet, sheet, or dedicated screen with `ListView` | A dialog is too constrained |
45
+ | Multi-step task or complex form | Sheet or dedicated screen | The interaction has its own workflow |
46
+ | No content yet | Empty State inside the screen | This is screen state, not transient feedback |
47
+ | Initial or blocking work | Loading/Progress state in context | Communicates progress without inventing a message |
48
+
49
+ Reserve Dialogs for critical information. Do not use them merely because they
50
+ are easy to create. If an action is common and undoable, prefer immediate
51
+ execution plus Undo over confirmation.
52
+
53
+ ### Snackbar versus Toast
54
+
55
+ For app-owned foreground feedback, standardize on Snackbar. It supports the
56
+ app's semantic colors, accessibility, safe-area positioning, duration policy,
57
+ and one optional recovery action. Do not maintain both a custom Toast and a
58
+ Snackbar for the same job.
59
+
60
+ A platform Toast can remain only when the operating system owns the behavior or
61
+ when a documented platform fallback is necessary. It is not the default design
62
+ surface.
63
+
64
+ ### Banner versus Snackbar
65
+
66
+ - Use Snackbar when the event has happened and the message can disappear.
67
+ - Use Banner or inline notice while an ongoing condition remains relevant.
68
+ - Never keep a Snackbar indefinitely to simulate persistent screen state.
69
+
70
+ ### Bottom Sheet versus Dialog
71
+
72
+ - Use Bottom Sheet for related choices: sorting, exporting, selecting a category,
73
+ or choosing an action for an item.
74
+ - Use Dialog for acknowledgement or a consequential confirm/cancel decision.
75
+ - Move long prose, structured data, search, or complex input into a sheet or
76
+ dedicated screen.
77
+
78
+ ## App-owned versus system-owned
79
+
80
+ Recreate and brand a surface only when the app owns its content and behavior.
81
+ Keep the platform UI when the system owns trust, permissions, identity, files,
82
+ or another protected workflow.
83
+
84
+ | Keep native/system-owned | May be app-owned and styled |
85
+ | --- | --- |
86
+ | Permission prompts | Success, warning, and error feedback |
87
+ | File and document pickers | Destructive confirmations |
88
+ | Share sheets and activity views | Contextual action lists |
89
+ | Date/time pickers when the platform interaction is desired | Category and app-data selectors |
90
+ | Camera and media authorization UI | Loading, empty, and error states |
91
+ | Biometric authentication prompt | Banners and inline validation |
92
+ | Keyboard, autofill, purchases, notifications, and OS settings | App-specific onboarding or education |
93
+
94
+ An app may explain why a system surface is about to appear, but it must not
95
+ imitate the trusted system prompt. Use the official Titanium API or native module
96
+ for the system-owned part.
97
+
98
+ Cancellation is often a normal system outcome. Do not turn every cancellation
99
+ into an error, a "Cancelled" message, or an unsupported success claim. Document
100
+ silence explicitly at the call site when it is meaningful.
101
+
102
+ ## What to standardize next
103
+
104
+ After Snackbar, Dialog, and Bottom Sheet, the highest-value shared patterns are:
105
+
106
+ 1. **Banner / Inline Notice** for persistent app state.
107
+ 2. **Loading / Progress wrapper** that uses the native activity indicator but
108
+ standardizes placement, text, blocking behavior, and cleanup.
109
+ 3. **Empty / Error State** for consistent screen-level recovery.
110
+ 4. **Field Validation** for message placement, focus, accessibility, and colors.
111
+
112
+ Standardize buttons, cards, badges, chips, skeletons, and coach marks only when
113
+ real product repetition exposes a stable contract. A visual resemblance alone
114
+ does not justify a Widget.
115
+
116
+ ## Phone, tablet, and accessibility
117
+
118
+ - On phones, present contextual choices from the bottom and keep touch targets
119
+ reachable.
120
+ - On tablets and large screens, use a centered, width-limited action surface or
121
+ a platform-appropriate anchored presentation; do not stretch phone geometry
122
+ edge to edge.
123
+ - Respect safe areas, bottom navigation, orientation, split screen, text scaling,
124
+ and localized strings.
125
+ - Announce transient feedback appropriately and move accessibility focus into a
126
+ modal surface when it opens.
127
+ - While a modal is active, prevent interaction with and accessibility traversal
128
+ into the underlying content. Restore the exact previous state on close.
129
+ - Back, backdrop, Cancel, and destructive actions must have predictable semantics
130
+ and must never execute callbacks more than once.
131
+
132
+ ## References
133
+
134
+ - [Titanium Alloy Widgets](https://titaniumsdk.com/guide/Alloy_Framework/Alloy_Guide/Alloy_Widgets.html)
135
+ - [Apple Human Interface Guidelines: Alerts](https://developer.apple.com/design/human-interface-guidelines/alerts)
136
+ - [Apple Human Interface Guidelines: Modality](https://developer.apple.com/design/human-interface-guidelines/modality)
137
+ - [Material Design 3: Snackbar](https://m3.material.io/components/snackbar/overview)
138
+ - [Material Design 3: Dialogs](https://m3.material.io/components/dialogs/overview)
139
+ - [Material Design 3: Bottom sheets](https://m3.material.io/components/bottom-sheets/overview)
140
+
@@ -0,0 +1,213 @@
1
+ # Contracts for feedback Widgets
2
+
3
+ Use these contracts for app-owned Snackbar, Dialog, and Bottom Sheet surfaces in
4
+ Alloy. Namespaces and visual tokens belong to each app; behavior stays stable.
5
+
6
+ ## Contents
7
+
8
+ - [Boundary and registration](#boundary-and-registration)
9
+ - [Snackbar](#snackbar)
10
+ - [Dialog](#dialog)
11
+ - [Bottom Sheet](#bottom-sheet)
12
+ - [Shared lifecycle contract](#shared-lifecycle-contract)
13
+ - [PurgeTSS integration](#purgetss-integration)
14
+ - [Widget, Require, state, or native UI](#widget-require-state-or-native-ui)
15
+
16
+ ## Boundary and registration
17
+
18
+ Implement each surface as an independent Alloy Widget under `app/widgets/`.
19
+ Each Widget owns its XML, controller, optional styles, assets, state, listeners,
20
+ timers, and `widget.json`. Keep them independent from any third-party component
21
+ library — including TiKit, the Alloy/PurgeTSS component set. A feedback surface
22
+ must not require one to work, and must not be folded into one: its value comes
23
+ from being installable and testable on its own.
24
+
25
+ Register every Widget in `app/config.json`:
26
+
27
+ ```json
28
+ {
29
+ "dependencies": {
30
+ "com.example.ui.bottomsheet": "1.0.0",
31
+ "com.example.ui.dialog": "1.0.0",
32
+ "com.example.ui.snackbar": "1.0.0"
33
+ }
34
+ }
35
+ ```
36
+
37
+ Use the app's reverse-domain namespace. The dependency key must match the `id`
38
+ in that Widget's `widget.json`.
39
+
40
+ Declare Widgets in XML, normally as the last visual children of the owning
41
+ window so overlays render above screen content:
42
+
43
+ ```xml
44
+ <Widget id="snackbar" src="com.example.ui.snackbar" />
45
+ <Widget id="dialog" src="com.example.ui.dialog" />
46
+ <Widget id="bottomSheet" src="com.example.ui.bottomsheet" />
47
+ ```
48
+
49
+ Build the stable proxy tree in XML. JavaScript may update properties and supply
50
+ `ListView` data; it must not reconstruct the complete surface imperatively.
51
+
52
+ ## Snackbar
53
+
54
+ ```javascript
55
+ $.snackbar.show({
56
+ message,
57
+ tone: 'neutral', // neutral | success | warning | error
58
+ duration: 4500,
59
+ actionTitle,
60
+ onAction,
61
+ onDismiss
62
+ })
63
+
64
+ $.snackbar.dismiss()
65
+ $.snackbar.destroy()
66
+ ```
67
+
68
+ Contract:
69
+
70
+ - A new message replaces the visible message and restarts the timer.
71
+ - Default `duration` is 4500 ms; `duration: 0` remains visible until an action,
72
+ `dismiss()`, replacement, or destruction.
73
+ - Allow at most one text action, such as Undo.
74
+ - Invoke `onAction` once, then close the Snackbar.
75
+ - Invoke `onDismiss` once with the reason `timeout`, `action`, `replaced`,
76
+ `programmatic`, or `destroy`.
77
+ - Clear the old timer before replacement and on every close path.
78
+ - Position above bottom navigation and inside the current safe area.
79
+ - Queueing Snackbars is not the default. The newest relevant message wins.
80
+
81
+ ## Dialog
82
+
83
+ ```javascript
84
+ $.dialog.attach(window)
85
+
86
+ $.dialog.show({
87
+ title,
88
+ message,
89
+ tone: 'neutral', // neutral | warning | error
90
+ confirmTitle,
91
+ cancelTitle,
92
+ destructive: false,
93
+ onConfirm,
94
+ onCancel
95
+ })
96
+
97
+ $.dialog.dismiss('programmatic')
98
+ $.dialog.destroy()
99
+ ```
100
+
101
+ Contract:
102
+
103
+ - Without `cancelTitle`, present an acknowledgement. With it, present a
104
+ confirmation.
105
+ - Enqueue additional `show()` calls FIFO. Never stack two Dialogs visually.
106
+ - Disable both buttons on the first accepted tap.
107
+ - Invoke `onConfirm` or `onCancel` at most once; never both.
108
+ - Cancel button, Android Back, or allowed backdrop dismissal invokes `onCancel`.
109
+ Android Back closes an acknowledgement as its single dismissal action.
110
+ - A destructive confirmation must not close from a backdrop tap.
111
+ - `dismiss('programmatic')` closes without treating the operation as a user
112
+ confirmation or cancellation.
113
+ - `destroy()` clears the active request and queue without running business
114
+ callbacks.
115
+ - Use platform-familiar safe/cancel ordering and a distinct destructive role;
116
+ do not hard-code one platform's button order for both.
117
+
118
+ ## Bottom Sheet
119
+
120
+ ```javascript
121
+ $.bottomSheet.attach(window)
122
+
123
+ $.bottomSheet.show({
124
+ title,
125
+ message,
126
+ items: [{
127
+ id,
128
+ title,
129
+ subtitle,
130
+ icon,
131
+ tone: 'neutral',
132
+ enabled: true
133
+ }],
134
+ selectedId,
135
+ onSelect,
136
+ onCancel
137
+ })
138
+
139
+ $.bottomSheet.dismiss('cancel')
140
+ $.bottomSheet.destroy()
141
+ ```
142
+
143
+ Contract:
144
+
145
+ - Render items with a fixed `ListView` template and data objects. Support long
146
+ lists, disabled rows, optional subtitles/icons, and a selected marker.
147
+ - Enqueue additional `show()` calls FIFO. Never stack sheets.
148
+ - Accept the first enabled selection only, invoke `onSelect(item)` once, and
149
+ close.
150
+ - Backdrop, Android Back, Cancel, or `dismiss('cancel')` invokes `onCancel` once.
151
+ - Other programmatic dismissal and `destroy()` do not imply cancellation and do
152
+ not execute business callbacks.
153
+ - Present from the bottom on phones. On tablets and large screens, use a centered,
154
+ width-limited action surface. Reserve `anchor` for a future anchored variant;
155
+ do not expose behavior that is not implemented.
156
+
157
+ ## Shared lifecycle contract
158
+
159
+ - Prefer one instance of each needed Widget per window. Add a global coordinator
160
+ only after real cross-window requirements exist.
161
+ - Call `attach(window)` after the host window/controller exists. Call `destroy()`
162
+ from the host's close cleanup.
163
+ - Make `destroy()` idempotent. Remove listeners, clear timers, cancel pending
164
+ animation callbacks, clear queues, and release callback references.
165
+ - Guard every asynchronous completion with the current presentation generation
166
+ so a stale timer or animation cannot dismiss a newer request.
167
+ - Consume backdrop events and prevent them from bubbling to underlying controls.
168
+ - Save the host's previous accessibility state, exclude underlying content while
169
+ modal, move focus to the surface, and restore the saved state on close.
170
+ - Keep localized copy outside the Widget. Pass strings and business callbacks in
171
+ the public API.
172
+ - Keep business logic out of the Widget. It reports a choice; the host performs
173
+ domain work.
174
+ - Verify every callback path for exactly-once behavior: action, confirm, cancel,
175
+ Back, backdrop, timeout, replacement, programmatic dismissal, and destruction.
176
+
177
+ ## PurgeTSS integration
178
+
179
+ When `purgetss/` or `purgetss/config.cjs` exists, load the `purgetss` skill before
180
+ writing styles. `ti-expert` decides the component boundary; `purgetss` decides
181
+ the verified utilities and configuration.
182
+
183
+ Apply these architectural rules:
184
+
185
+ - Set PurgeTSS `options.widgets` to `true` so Widget XML is scanned.
186
+ - Express stable anatomy and utility classes in Widget XML.
187
+ - Use semantic color names for surfaces, text, borders, accent, overlay, success,
188
+ warning, and error. Let Titanium resolve light/dark variants.
189
+ - Do not edit generated `app/styles/app.tss`.
190
+ - Use `widget.tss` only for properties that have no verified utility or that are
191
+ inherently specific to the Widget.
192
+ - Declare animations as XML `Animation` proxies using `purgetss.ui`. Use explicit
193
+ `open()` and `close()` calls instead of toggle behavior.
194
+ - After generation, inspect the final `app.tss` section for unused or unsupported
195
+ classes and resolve every new entry.
196
+
197
+ Do not copy class inventories into this skill and do not guess Tailwind-like
198
+ names. The installed PurgeTSS skill remains the source of truth.
199
+
200
+ ## Widget, Require, state, or native UI
201
+
202
+ | Choose | When |
203
+ | --- | --- |
204
+ | Alloy Widget | Self-contained public API, state/lifecycle ownership, portable boundary, or reuse across apps/screens |
205
+ | `<Require>` controller | App-specific view/controller composition that shares the host's domain and lifecycle |
206
+ | Screen state in XML | Loading, empty, content, or error presentation belongs only to that screen |
207
+ | TSS or PurgeTSS utilities | Repeated visual treatment has no behavior, state, or lifecycle |
208
+ | Native/system UI | The operating system owns permissions, trust, files, sharing, identity, purchases, or another protected flow |
209
+
210
+ Usage count is evidence, not a threshold. A component can deserve a Widget on
211
+ its first use if it has a stable portable API and non-trivial lifecycle; three
212
+ similar labels still do not deserve one.
213
+
@@ -4,7 +4,8 @@
4
4
  ## Contents
5
5
 
6
6
  - [Assessment checklist](#assessment-checklist)
7
- - [Phase 1: TSS organization](#phase-1-tss-organization)
7
+ - [Phase 1: styling-system organization](#phase-1-styling-system-organization)
8
+ - [Phase 1B: feedback surfaces](#phase-1b-feedback-surfaces)
8
9
  - [Phase 2: service layer extraction](#phase-2-service-layer-extraction)
9
10
  - [Phase 3: event system migration](#phase-3-event-system-migration)
10
11
  - [Phase 4: navigation service](#phase-4-navigation-service)
@@ -21,24 +22,36 @@ Before migrating, evaluate the current codebase:
21
22
 
22
23
  | Area | Signs of Legacy Code | Target State |
23
24
  | ----------- | -------------------------------------------------- | --------------------------------------------- |
24
- | Styling | Inline attributes, scattered styles | Organized TSS files (per-controller + global) |
25
+ | Styling | Inline attributes, scattered styles | PurgeTSS utilities when detected; otherwise organized Alloy TSS |
25
26
  | Controllers | 200+ lines, API calls, business logic | Thin orchestrators (<100 lines) |
26
27
  | Events | `Ti.App.fireEvent` everywhere | Backbone.Events or StateStore |
27
28
  | Navigation | Direct `Alloy.createController().getView().open()` | Navigation service |
28
29
  | Data | Scattered `Ti.App.Properties`, no collections | Centralized state + Collections |
29
30
 
30
- ## Phase 1: TSS organization
31
+ ## Phase 1: styling-system organization
31
32
 
32
- **Goal**: Migrate from inline styling and scattered styles to well-organized TSS files.
33
+ **Goal**: Migrate from inline and scattered styling into the styling system the
34
+ project actually uses.
35
+
36
+ ### Step 0: detect PurgeTSS
37
+
38
+ Check for `purgetss/` or `purgetss/config.cjs` before editing styles.
39
+
40
+ - **PurgeTSS detected:** invoke the `purgetss` skill, use verified utilities and
41
+ semantic colors, configure reusable rules in `config.cjs`, and never edit the
42
+ generated `app/styles/app.tss`.
43
+ - **No PurgeTSS:** use standard per-controller TSS and a manually maintained
44
+ `app/styles/app.tss` for genuinely global selectors.
45
+
46
+ Do not migrate a PurgeTSS application back into manual TSS by following a generic
47
+ Alloy example.
33
48
 
34
49
  ### Step 1: audit existing styles
35
50
 
36
- Find all inline attributes and consolidate into TSS files:
51
+ Find inline attributes and classify them for the detected styling system:
37
52
  ```bash
38
53
  # Find inline styling in XML views
39
- grep -r 'backgroundColor=' app/views/
40
- grep -r 'font=' app/views/
41
- grep -r 'color=' app/views/
54
+ rg -n 'backgroundColor=|font=|color=' app/views/
42
55
  ```
43
56
 
44
57
  ### Step 2: move inline attributes to TSS
@@ -51,7 +64,7 @@ grep -r 'color=' app/views/
51
64
  ```
52
65
 
53
66
  ```xml
54
- <!-- AFTER: Clean XML + TSS file -->
67
+ <!-- AFTER in standard Alloy: clean XML + TSS file -->
55
68
  <View id="header">
56
69
  <Label id="title" text="L('welcome')" />
57
70
  </View>
@@ -63,9 +76,11 @@ grep -r 'color=' app/views/
63
76
  "#title": { color: "#333", font: { fontSize: 18, fontWeight: "bold" } }
64
77
  ```
65
78
 
66
- ### Step 3: organize TSS structure
79
+ ### Step 3: organize standard Alloy TSS
80
+
81
+ This step applies only when PurgeTSS is not present.
67
82
 
68
- 1. Use `app.tss` for global styles (shared across all views)
83
+ 1. Use `app.tss` for genuinely global styles shared across views
69
84
  2. Use per-controller TSS files for view-specific styles
70
85
  3. Use class selectors for reusable style patterns:
71
86
  ```tss
@@ -74,6 +89,18 @@ grep -r 'color=' app/views/
74
89
  ".btn-primary": { backgroundColor: '#2563eb', color: '#fff', height: 44, borderRadius: 8 }
75
90
  ```
76
91
 
92
+ In either styling path, replace hard-coded visual colors with semantic roles as
93
+ part of the migration. See [Theming and Dark Mode](theming.md).
94
+
95
+ ## Phase 1B: feedback surfaces
96
+
97
+ Inventory native alerts, option dialogs, Toasts, and local overlays. Classify
98
+ each occurrence before replacing it, then migrate one window at a time. Preserve
99
+ system-owned pickers, permission prompts, sharing, and biometric UI.
100
+
101
+ Use [Feedback Migration](feedback-migration.md) for the sequence, deliberate
102
+ silence rules, and verification contract.
103
+
77
104
  ## Phase 2: service layer extraction
78
105
 
79
106
  **Goal**: Move business logic from controllers to services.
@@ -280,11 +307,12 @@ const { user } = appStore.getState()
280
307
 
281
308
  Recommended sequence to minimize risk:
282
309
 
283
- 1. **TSS Organization** - Visual only, no logic changes
284
- 2. **Service Layer** - Extract logic without changing behavior
285
- 3. **Navigation Service** - Centralize with cleanup
286
- 4. **Event System** - Replace Ti.App events
287
- 5. **State Management** - Last, as it touches everything
310
+ 1. **Styling-system organization** - Establish semantic tokens and the correct PurgeTSS/Alloy path
311
+ 2. **Feedback surfaces** - Migrate one window without changing domain outcomes
312
+ 3. **Service Layer** - Extract logic without changing behavior
313
+ 4. **Navigation Service** - Centralize with cleanup
314
+ 5. **Event System** - Replace Ti.App events
315
+ 6. **State Management** - Last, as it touches everything
288
316
 
289
317
  ## Rollback strategy
290
318
 
@@ -293,16 +321,10 @@ For each phase:
293
321
  1. **Create a branch** before starting
294
322
  2. **Migrate one feature** completely
295
323
  3. **Test thoroughly** before moving to next feature
296
- 4. **Keep legacy code** commented until stable
324
+ 4. **Delete the replaced implementation** after the vertical slice passes
297
325
 
298
- ```javascript
299
- // Temporary: Keep both during migration
300
- // Legacy:
301
- // Ti.App.fireEvent('user:updated', data)
302
-
303
- // New:
304
- Alloy.Events.trigger('user:updated', data)
305
- ```
326
+ Use version control for rollback. Do not keep commented legacy implementations:
327
+ they conceal duplicate listeners, stale callbacks, and incomplete migrations.
306
328
 
307
329
  ## Common migration pitfalls
308
330