wize-dev-kit 0.1.4 → 0.1.5
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.
- package/CHANGELOG.md +39 -1
- package/package.json +1 -1
- package/src/app-overlay/playbooks/apple-hig.md +112 -0
- package/src/app-overlay/playbooks/detox-maestro.md +179 -0
- package/src/app-overlay/playbooks/device-matrix.md +121 -0
- package/src/app-overlay/playbooks/material-design-3.md +135 -0
- package/src/app-overlay/playbooks/mobile-perf-budgets.md +145 -0
- package/src/app-overlay/playbooks/permissions-ux.md +147 -0
- package/src/app-overlay/playbooks/touch-targets-and-gestures.md +127 -0
- package/src/app-overlay/stack-catalog.md +178 -0
- package/src/web-overlay/playbooks/playwright-vitest.md +211 -0
- package/src/web-overlay/playbooks/responsive-breakpoints.md +104 -0
- package/src/web-overlay/playbooks/semantic-html.md +114 -0
- package/src/web-overlay/playbooks/wcag-aa.md +97 -0
- package/src/web-overlay/playbooks/web-perf-budgets.md +140 -0
- package/src/web-overlay/stack-catalog.md +208 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: mobile-perf-budgets
|
|
3
|
+
owner: wize-agent-test-architect # Hawkeye (with Tony for platform tuning)
|
|
4
|
+
applies_when: app-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mobile Performance Budgets — Hawkeye Playbook
|
|
9
|
+
|
|
10
|
+
Mobile users measure your app in milliseconds and megabytes. Budgets are how you protect both.
|
|
11
|
+
|
|
12
|
+
## 1. The numbers that matter
|
|
13
|
+
|
|
14
|
+
| Metric | Good | Needs work | Poor |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| **Cold start** (launch → first frame) | < 1.5s | 1.5–2.5s | > 2.5s |
|
|
17
|
+
| **Warm start** | < 0.8s | 0.8–1.5s | > 1.5s |
|
|
18
|
+
| **Time to interactive** (TTI) | < 2.0s | 2.0–3.5s | > 3.5s |
|
|
19
|
+
| **Frame rate** (sustained) | 60 fps stable | drops < 5/sec | drops > 5/sec |
|
|
20
|
+
| **Jank** (frames > 16ms) | < 0.5% | 0.5–2% | > 2% |
|
|
21
|
+
| **App size** (download) | < 50 MB | 50–150 MB | > 150 MB |
|
|
22
|
+
| **App size** (installed) | < 200 MB | 200–500 MB | > 500 MB |
|
|
23
|
+
| **Memory** (steady-state) | < 150 MB | 150–300 MB | > 300 MB |
|
|
24
|
+
| **Battery drain** (per hour, foreground) | < 5% | 5–10% | > 10% |
|
|
25
|
+
|
|
26
|
+
Targets for **mid-range phones, not your flagship**. Hawkeye runs benchmarks on the device matrix (see `device-matrix.md`).
|
|
27
|
+
|
|
28
|
+
## 2. App size budget
|
|
29
|
+
|
|
30
|
+
### Per-platform ceiling
|
|
31
|
+
|
|
32
|
+
- iOS App Store: 200 MB OTA download cellular ceiling. Plan for **< 150 MB** to leave headroom.
|
|
33
|
+
- Google Play: 150 MB APK ceiling; use App Bundle splits. Plan for **< 30 MB** initial download.
|
|
34
|
+
|
|
35
|
+
### Where size goes
|
|
36
|
+
|
|
37
|
+
Audit on every release:
|
|
38
|
+
|
|
39
|
+
| Slice | Tool |
|
|
40
|
+
|---|---|
|
|
41
|
+
| RN / Expo JS bundle | `metro-visualizer`, `react-native-bundle-visualizer` |
|
|
42
|
+
| Native deps | Xcode Linker map / Android Studio APK Analyzer |
|
|
43
|
+
| Assets (images, fonts, video) | Compare to design tokens — anything unused? |
|
|
44
|
+
| Third-party SDKs | Each SDK > 1 MB needs a written justification |
|
|
45
|
+
|
|
46
|
+
### Reductions that work
|
|
47
|
+
|
|
48
|
+
1. **Vector over bitmap** wherever the design allows (PDF for iOS asset catalog, VectorDrawable for Android).
|
|
49
|
+
2. **WebP/AVIF over PNG** for bitmaps.
|
|
50
|
+
3. **Subset fonts**; ship one weight, derive others via OS faux-bold when acceptable.
|
|
51
|
+
4. **Tree-shake JS bundle** (Hermes, ProGuard/R8). Verify with the visualizer.
|
|
52
|
+
5. **App Thinning** (iOS) and **App Bundle splits** (Android) so each user downloads only their architecture/density.
|
|
53
|
+
6. **On-demand resources** (iOS) / **Play Asset Delivery** (Android) for content not needed at launch.
|
|
54
|
+
|
|
55
|
+
## 3. Cold start
|
|
56
|
+
|
|
57
|
+
Cold start is dominated by:
|
|
58
|
+
|
|
59
|
+
1. **Process launch** — Apple/Google control, but linking and dyld matter (fewer, smaller binaries help).
|
|
60
|
+
2. **Framework init** — every SDK initialized in `application(_:didFinishLaunching)` adds to it. Defer non-essential to first-screen-rendered.
|
|
61
|
+
3. **First view render** — keep the launch screen → first interactive screen path empty of heavy work.
|
|
62
|
+
|
|
63
|
+
### Patterns
|
|
64
|
+
|
|
65
|
+
- Lazy-init analytics, crash reporting, ad SDKs, A/B testing — after first frame.
|
|
66
|
+
- Pre-render the first screen's skeleton; populate from cache; refresh in background.
|
|
67
|
+
- For RN/Expo: enable **Hermes** + **bundleAssetName splitting**.
|
|
68
|
+
- For Android: keep Application.onCreate empty; init via lifecycle observer.
|
|
69
|
+
|
|
70
|
+
### Measurement
|
|
71
|
+
|
|
72
|
+
| Platform | Tool |
|
|
73
|
+
|---|---|
|
|
74
|
+
| iOS | Xcode Instruments → "App Launch", Time Profiler |
|
|
75
|
+
| Android | Macrobenchmark library (Jetpack) for ColdStartup. `adb shell am start -W` for rough numbers. |
|
|
76
|
+
| RN | `Performance.now()` markers + Flipper Perf plugin. |
|
|
77
|
+
|
|
78
|
+
## 4. Frame rate & jank
|
|
79
|
+
|
|
80
|
+
60 fps = 16.67ms/frame. 120 fps target on ProMotion / 90 Hz Androids = 8.33ms.
|
|
81
|
+
|
|
82
|
+
### Common culprits
|
|
83
|
+
|
|
84
|
+
- **Layout thrashing** — measuring layout inside scroll/animation handlers.
|
|
85
|
+
- **Image decoding on main thread** — decode async, cache decoded.
|
|
86
|
+
- **Synchronous bridge calls (RN)** — batch updates, use native modules.
|
|
87
|
+
- **Heavy `onLayout` / `LaunchedEffect` on each render**.
|
|
88
|
+
- **Re-rendering large lists** — use `FlatList` with `keyExtractor` + `getItemLayout`, or `LazyColumn`.
|
|
89
|
+
|
|
90
|
+
### Measurement
|
|
91
|
+
|
|
92
|
+
- iOS: Instruments → Core Animation → FPS.
|
|
93
|
+
- Android: GPU profiling overlay + Macrobenchmark FrameTimingMetric.
|
|
94
|
+
- RN: Flipper Perf, `react-native-performance`.
|
|
95
|
+
|
|
96
|
+
## 5. Memory
|
|
97
|
+
|
|
98
|
+
- Aim for steady-state **< 150 MB** on mid-range.
|
|
99
|
+
- Audit images caches; cap at `min(50 MB, 100 thumbnails)`.
|
|
100
|
+
- Release video / audio assets on pause.
|
|
101
|
+
- Watch out for retain cycles (iOS) and Activity / Fragment leaks (Android) — use LeakCanary / Xcode memory graph.
|
|
102
|
+
|
|
103
|
+
## 6. Battery
|
|
104
|
+
|
|
105
|
+
- Don't poll. Use OS push (silent push), BLE listeners (low-power), location updates with low frequency.
|
|
106
|
+
- Background tasks: respect the OS budget (BGTaskScheduler / WorkManager).
|
|
107
|
+
- Avoid wake-locks; trade off freshness for energy.
|
|
108
|
+
|
|
109
|
+
## 7. Network
|
|
110
|
+
|
|
111
|
+
- HTTP/2 minimum; HTTP/3 where supported.
|
|
112
|
+
- gzip / brotli on responses.
|
|
113
|
+
- Aggressive cache headers; SWR (stale-while-revalidate) pattern for read-heavy data.
|
|
114
|
+
- Image CDN with on-the-fly format/quality negotiation.
|
|
115
|
+
- Don't fan out 30 small requests; batch with GraphQL or a dedicated endpoint.
|
|
116
|
+
|
|
117
|
+
## 8. Build-time enforcement
|
|
118
|
+
|
|
119
|
+
| Check | Tool |
|
|
120
|
+
|---|---|
|
|
121
|
+
| Bundle size growth > 5% per release | `danger-js` rule on the bundle visualizer output. |
|
|
122
|
+
| Cold start regression > 10% | Macrobenchmark in CI; fail PR on threshold. |
|
|
123
|
+
| Frame timing | Compose Compiler metrics for Android; Hermes profiler for RN. |
|
|
124
|
+
|
|
125
|
+
## 9. Field measurement
|
|
126
|
+
|
|
127
|
+
- iOS: `MetricKit` for power, hangs, scroll latency.
|
|
128
|
+
- Android: Firebase Performance + Macrobenchmark.
|
|
129
|
+
- Cross-platform: Sentry Performance, NewRelic Mobile.
|
|
130
|
+
|
|
131
|
+
Look at **p75** and **p90** in field data. The user is the 75th percentile, not the 50th.
|
|
132
|
+
|
|
133
|
+
## 10. Hawkeye's gate inputs
|
|
134
|
+
|
|
135
|
+
For each epic NFR review (`tea/nfr/{epic}.md`):
|
|
136
|
+
|
|
137
|
+
- Cold start (mid-range device).
|
|
138
|
+
- Bundle size delta vs last release.
|
|
139
|
+
- Steady-state memory.
|
|
140
|
+
- Critical-flow frame rate (one E2E run with FPS capture).
|
|
141
|
+
- Battery (instrumented test with consistent workload).
|
|
142
|
+
|
|
143
|
+
## 11. Hand-off
|
|
144
|
+
|
|
145
|
+
Fury sets the targets in `nfr-principles.md`. Tony picks the stack respecting the targets. Mantis designs without animation excess. Shuri implements. Hawkeye verifies on each release; regressions hit Concerns/Fail at the gate.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: permissions-ux
|
|
3
|
+
owner: wize-agent-ux-designer # Mantis (paired with Pepper on copy)
|
|
4
|
+
applies_when: app-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Permissions UX — Mantis Playbook
|
|
9
|
+
|
|
10
|
+
The system dialog you can't customize. Everything *around* it is yours. Use it well — denied permissions are usually a UX failure, not a privacy choice.
|
|
11
|
+
|
|
12
|
+
## 1. The four states for every permission
|
|
13
|
+
|
|
14
|
+
1. **Not determined** — system never asked.
|
|
15
|
+
2. **Granted** — you can use the feature.
|
|
16
|
+
3. **Denied** — user said no; system won't re-prompt; only Settings can flip.
|
|
17
|
+
4. **Restricted / Limited** — partial access (iOS photos limited library; Android approximate location).
|
|
18
|
+
|
|
19
|
+
Design **all four** for every permission your app uses.
|
|
20
|
+
|
|
21
|
+
## 2. The "pre-flight" pattern
|
|
22
|
+
|
|
23
|
+
Never trigger the system prompt as the first thing the user sees. Always:
|
|
24
|
+
|
|
25
|
+
1. **In-context trigger.** The user tries a feature that needs the permission.
|
|
26
|
+
2. **Mantis pre-flight UI.** A custom screen/sheet explains *why*, *what*, *what happens if no*.
|
|
27
|
+
3. **System prompt.** User decides.
|
|
28
|
+
4. **Outcome path.** Granted → feature works. Denied → degraded mode; offer Settings deep-link.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
[user taps "Add photo"]
|
|
32
|
+
↓
|
|
33
|
+
┌────────────────────────────────────┐
|
|
34
|
+
│ Why we need your photos │
|
|
35
|
+
│ To attach an image to your post. │
|
|
36
|
+
│ We never upload without confirm. │
|
|
37
|
+
│ │
|
|
38
|
+
│ [ Not now ] [ Continue ] │
|
|
39
|
+
└────────────────────────────────────┘
|
|
40
|
+
↓ Continue
|
|
41
|
+
[system prompt — uneditable]
|
|
42
|
+
↓
|
|
43
|
+
Granted → flow continues
|
|
44
|
+
Denied → fallback UI + "Open Settings"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 3. Per-permission guidance
|
|
48
|
+
|
|
49
|
+
### Camera & microphone
|
|
50
|
+
|
|
51
|
+
- Ask only when the user starts a recording/capture action.
|
|
52
|
+
- Pre-flight explains what's recorded, stored, and uploaded.
|
|
53
|
+
- If denied: show a permanent banner with "Open Settings" inside the feature; don't auto-re-prompt.
|
|
54
|
+
|
|
55
|
+
### Photo library
|
|
56
|
+
|
|
57
|
+
- iOS 14+: **Limited Library is the default**. Embrace it — show a "Manage" button to add more later.
|
|
58
|
+
- Android 13+: granular media access (images / video / audio).
|
|
59
|
+
- Don't ask for full library if you only need one picker — use `PHPickerViewController` / `Photo Picker` which need **no permission** at all.
|
|
60
|
+
|
|
61
|
+
### Location
|
|
62
|
+
|
|
63
|
+
- Pre-flight has a *map screenshot* showing the precision and the purpose ("nearby restaurants" vs "your route").
|
|
64
|
+
- Android 12+: approximate location is the default. Justify precise.
|
|
65
|
+
- Background location: **only** if the feature truly needs it (delivery, fitness route). Otherwise foreground-only.
|
|
66
|
+
|
|
67
|
+
### Notifications
|
|
68
|
+
|
|
69
|
+
- Don't ask at launch. Ask the first time a notification would actually be useful.
|
|
70
|
+
- Pre-flight: list the categories you'll use (mention, reminder, marketing). Tie each to a setting in-app.
|
|
71
|
+
- Provide per-category opt-in inside the app — match to OS notification channels (Android) / authorization options (iOS).
|
|
72
|
+
|
|
73
|
+
### Contacts / Calendar / Reminders
|
|
74
|
+
|
|
75
|
+
- High-friction permission; only when essential.
|
|
76
|
+
- If you just need a single contact, use the system picker — **no permission required**.
|
|
77
|
+
|
|
78
|
+
### Bluetooth / Local Network
|
|
79
|
+
|
|
80
|
+
- iOS: bluetooth-LE for peripherals only; Local Network for Bonjour discovery — both prompt.
|
|
81
|
+
- Pre-flight should explain it's for nearby devices, not internet access.
|
|
82
|
+
|
|
83
|
+
### Health / Motion (iOS)
|
|
84
|
+
|
|
85
|
+
- HealthKit asks per data type. Group requests by user-task, not by data field.
|
|
86
|
+
- Motion: pre-flight explains it's used for step-count or activity recognition only.
|
|
87
|
+
|
|
88
|
+
### Tracking (App Tracking Transparency, iOS)
|
|
89
|
+
|
|
90
|
+
- The system prompt is required if you track across other apps/sites.
|
|
91
|
+
- Apple disallows incentivizing the choice; respect it.
|
|
92
|
+
|
|
93
|
+
## 4. Copy template (Pepper helps phrase)
|
|
94
|
+
|
|
95
|
+
Pre-flight title: **"Why we need [permission]"**.
|
|
96
|
+
One-line value statement.
|
|
97
|
+
One-line "what we will not do" (privacy promise).
|
|
98
|
+
Two buttons: **Not now** (no permission requested) and **Continue** (triggers system prompt).
|
|
99
|
+
|
|
100
|
+
Avoid: "Allow Camera Access" (system already says that).
|
|
101
|
+
|
|
102
|
+
## 5. Denied state UI
|
|
103
|
+
|
|
104
|
+
Never silently fail. Show:
|
|
105
|
+
- The state badge ("Camera access off").
|
|
106
|
+
- A one-line reason.
|
|
107
|
+
- A direct **"Open Settings"** button.
|
|
108
|
+
|
|
109
|
+
```swift
|
|
110
|
+
// iOS
|
|
111
|
+
if let url = URL(string: UIApplication.openSettingsURLString) {
|
|
112
|
+
UIApplication.shared.open(url)
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```kotlin
|
|
117
|
+
// Android
|
|
118
|
+
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null)))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 6. Settings mirror inside the app
|
|
122
|
+
|
|
123
|
+
Every permission you request has a corresponding row in **Settings → Permissions** inside the app. Mirrors the OS state, with the same Open Settings deep-link. This is the user's escape hatch.
|
|
124
|
+
|
|
125
|
+
## 7. Background work
|
|
126
|
+
|
|
127
|
+
Some Android/iOS background permissions (location, background refresh, BLE in background) require user education and energy disclosure. Pre-flight mentions battery cost explicitly.
|
|
128
|
+
|
|
129
|
+
## 8. Don'ts
|
|
130
|
+
|
|
131
|
+
- Multiple prompts on first launch ("camera, mic, location, notifications" in a row). Cardinal sin.
|
|
132
|
+
- Re-asking immediately after denial — many OSes block; users get frustrated.
|
|
133
|
+
- Using "we can't function without this" as the reason — design for the denied state.
|
|
134
|
+
- Pre-flight that already says "tap Allow" — the system dialog handles that. Just convince them *why*.
|
|
135
|
+
- Modal blocking UI when the user denies. The feature degrades; the app doesn't.
|
|
136
|
+
|
|
137
|
+
## 9. Audit list
|
|
138
|
+
|
|
139
|
+
For every release, Hawkeye verifies in `tea-design.md`:
|
|
140
|
+
- Every permission has a pre-flight UI.
|
|
141
|
+
- Every permission has a denied-state UI.
|
|
142
|
+
- Every permission has a Settings deep-link.
|
|
143
|
+
- App functions (degraded) without each non-essential permission.
|
|
144
|
+
|
|
145
|
+
## 10. Hand-off
|
|
146
|
+
|
|
147
|
+
Mantis owns flow + copy strings. Pepper polishes copy. Tony picks the entitlement / manifest. Shuri implements; Hawkeye walks the four states (not-asked / granted / denied / limited) per permission.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
playbook: touch-targets-and-gestures
|
|
3
|
+
owner: wize-agent-ux-designer # Mantis
|
|
4
|
+
applies_when: app-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Touch Targets & Gestures — Mantis Playbook
|
|
9
|
+
|
|
10
|
+
The thumb is the cursor. Design every screen around how it actually reaches the controls.
|
|
11
|
+
|
|
12
|
+
## 1. Minimum touch target sizes
|
|
13
|
+
|
|
14
|
+
| Platform | Minimum | Recommended primary |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **iOS** (HIG) | 44×44 pt | 48×48+ pt for primary CTAs |
|
|
17
|
+
| **Android** (Material) | 48×48 dp | 56×56 dp for FABs |
|
|
18
|
+
| **Web** (WCAG 2.5.8) | 24×24 CSS px | 44×44 CSS px for primary |
|
|
19
|
+
|
|
20
|
+
If a visible glyph must be smaller (icon-only toolbar), expand the **hit area** via padding or `hitSlop` — the visual icon stays small but the touch is large.
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
// SwiftUI hit area
|
|
24
|
+
Button { /* */ } label: { Image(systemName: "star") }
|
|
25
|
+
.contentShape(Rectangle())
|
|
26
|
+
.frame(minWidth: 44, minHeight: 44)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// Compose
|
|
31
|
+
Modifier.minimumInteractiveComponentSize() // ≥ 48dp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```jsx
|
|
35
|
+
// React Native
|
|
36
|
+
<Pressable hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>...</Pressable>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 2. Spacing between adjacent targets
|
|
40
|
+
|
|
41
|
+
- **≥ 8 dp/pt** between adjacent interactive elements.
|
|
42
|
+
- Bottom row of nav must clear the home indicator (iOS) / gesture region (Android).
|
|
43
|
+
|
|
44
|
+
## 3. Thumb reach zones (one-handed use)
|
|
45
|
+
|
|
46
|
+
Three zones, on a 6.1" phone:
|
|
47
|
+
|
|
48
|
+
| Zone | Region | Use for |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| **Easy** | Bottom 1/3 | Primary actions, nav. |
|
|
51
|
+
| **OK** | Middle 1/3 | Secondary, content tap. |
|
|
52
|
+
| **Hard** | Top 1/3 | Status, branding, low-frequency. |
|
|
53
|
+
|
|
54
|
+
Put destructive or rarely-used controls in the **Hard** zone. Put the primary CTA in **Easy**.
|
|
55
|
+
|
|
56
|
+
## 4. Standard gestures (use the system's vocabulary)
|
|
57
|
+
|
|
58
|
+
| Gesture | Reserved meaning |
|
|
59
|
+
|---|---|
|
|
60
|
+
| **Tap** | Default action. |
|
|
61
|
+
| **Long press** | Reveal secondary actions / context menu. Optional path — never the only one. |
|
|
62
|
+
| **Swipe left/right on row** | Quick actions (archive, delete). |
|
|
63
|
+
| **Swipe down on modal** | Dismiss. |
|
|
64
|
+
| **Pull down on list** | Refresh. |
|
|
65
|
+
| **Pinch** | Zoom (content). |
|
|
66
|
+
| **Two-finger drag** | Multi-select (mac/iPad). |
|
|
67
|
+
| **Edge swipe (iOS)** | Back navigation. |
|
|
68
|
+
| **System back gesture (Android)** | Back; **don't** intercept lightly. |
|
|
69
|
+
|
|
70
|
+
## 5. Custom gestures — pick wisely
|
|
71
|
+
|
|
72
|
+
If you must invent a gesture:
|
|
73
|
+
|
|
74
|
+
1. **Don't make it the only path.** Pair with a visible tap affordance.
|
|
75
|
+
2. **Onboard once.** Show the gesture on first launch; never again.
|
|
76
|
+
3. **Make it cancellable.** Mid-gesture the user must be able to abort.
|
|
77
|
+
4. **Don't fight system gestures.** Edge swipes on iOS = back; you'll lose.
|
|
78
|
+
5. **Test with system Voice Control** (iOS) / Switch Access (Android) — discoverable to users who can't gesture.
|
|
79
|
+
|
|
80
|
+
## 6. Multi-touch caveats
|
|
81
|
+
|
|
82
|
+
- iOS: `MultipleTouchEnabled = true` per view; default is single.
|
|
83
|
+
- Android: track pointer IDs on every event; don't assume one finger.
|
|
84
|
+
- Two-finger gestures (pinch, rotate) need an alternative for users with motor limitations — usually a button.
|
|
85
|
+
|
|
86
|
+
## 7. Drag-and-drop
|
|
87
|
+
|
|
88
|
+
| Platform | Native |
|
|
89
|
+
|---|---|
|
|
90
|
+
| iOS | `UIDragInteraction` / SwiftUI `.draggable + .dropDestination` |
|
|
91
|
+
| Android | `View.startDragAndDrop` / Compose `dragGestures` |
|
|
92
|
+
| React Native | community lib (e.g., `react-native-draggable-flatlist`) |
|
|
93
|
+
|
|
94
|
+
Drag-and-drop must:
|
|
95
|
+
- Show a visible drag preview.
|
|
96
|
+
- Highlight valid drop targets.
|
|
97
|
+
- Support keyboard equivalent (or selection + "Move to…") for accessibility (WCAG 2.5.7).
|
|
98
|
+
|
|
99
|
+
## 8. Haptics
|
|
100
|
+
|
|
101
|
+
| Use | Pattern |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Successful confirmation | Light impact (.light / `VibrationEffect.EFFECT_TICK`) |
|
|
104
|
+
| Failure / invalid | Notification error / double tick |
|
|
105
|
+
| Long-press triggered | Selection change |
|
|
106
|
+
| Pull-to-refresh release | Light impact |
|
|
107
|
+
|
|
108
|
+
Haptics are noise without a system event behind them. Don't trigger them on every tap.
|
|
109
|
+
|
|
110
|
+
## 9. Reduce Motion / Reduce Transparency
|
|
111
|
+
|
|
112
|
+
Reduce gesture-driven motion: replace swipe-to-reveal animations with simple cross-fades. Detect with:
|
|
113
|
+
|
|
114
|
+
- iOS: `UIAccessibility.isReduceMotionEnabled`
|
|
115
|
+
- Android: `Settings.Global.TRANSITION_ANIMATION_SCALE`
|
|
116
|
+
|
|
117
|
+
## 10. Don'ts
|
|
118
|
+
|
|
119
|
+
- Targets smaller than the platform minimum, even if "the icon is small".
|
|
120
|
+
- Hover-only affordances (no hover on touch).
|
|
121
|
+
- Gestures that conflict with system gestures (edge swipes, pull-from-top).
|
|
122
|
+
- Long-press-only access to a critical feature.
|
|
123
|
+
- "Discover this gesture by trial" — onboarding it costs less than the support tickets.
|
|
124
|
+
|
|
125
|
+
## 11. Hand-off
|
|
126
|
+
|
|
127
|
+
Mantis annotates every interactive element with: `hit area`, `gesture`, `haptic`, `state on disabled`. Shuri implements; Hawkeye verifies hit-area sizes in `tea-design.md` using device-pixel measurements.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
catalog: app-stack
|
|
3
|
+
owner: wize-agent-architect # Tony (with Fury on strategy)
|
|
4
|
+
applies_when: app-overlay active
|
|
5
|
+
status: ready
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# App Stack Catalog — Tony's Reference
|
|
9
|
+
|
|
10
|
+
The mobile stack decision is harder to reverse than the web one. Pick deliberately. This catalog lists each option with the **trade-off honest enough that you don't relitigate it in six months**.
|
|
11
|
+
|
|
12
|
+
## 1. Decision dimensions
|
|
13
|
+
|
|
14
|
+
Order them: **distribution → team → performance → reuse**.
|
|
15
|
+
|
|
16
|
+
1. **Distribution** — App Store + Play Store only? B2B enterprise (TestFlight/internal)? Sideload?
|
|
17
|
+
2. **Team** — primary skill set: web, native (Swift/Kotlin), Flutter (Dart)?
|
|
18
|
+
3. **Performance** — animation-heavy / camera / AR / ML on-device? Or list + form?
|
|
19
|
+
4. **Code reuse** — does a web app share business logic? How much?
|
|
20
|
+
5. **Hiring tail** — what skill set will be easy to hire from in 2 years?
|
|
21
|
+
|
|
22
|
+
## 2. Frameworks
|
|
23
|
+
|
|
24
|
+
### React Native + Expo (managed workflow)
|
|
25
|
+
|
|
26
|
+
| | |
|
|
27
|
+
|---|---|
|
|
28
|
+
| **Pick when** | Team is JS/TS-strong; cross-platform with one codebase + fast iteration. |
|
|
29
|
+
| **Strengths** | EAS Build/Update/Submit; OTA updates; same team can ship the web product. |
|
|
30
|
+
| **Costs** | Native modules sometimes need ejecting; SDK lag for very-new OS features. |
|
|
31
|
+
| **Perf ceiling** | 60 fps for typical UIs; deep native work requires bridging or custom modules. |
|
|
32
|
+
| **Best for** | Most cross-platform B2C apps with shared web team. |
|
|
33
|
+
|
|
34
|
+
### React Native (bare / community CLI)
|
|
35
|
+
|
|
36
|
+
| | |
|
|
37
|
+
|---|---|
|
|
38
|
+
| **Pick when** | Need native modules Expo doesn't ship + full control over Xcode/Gradle. |
|
|
39
|
+
| **Strengths** | Maximum flexibility, integrate any SDK. |
|
|
40
|
+
| **Costs** | Maintain native build configs; lose EAS conveniences (unless self-hosted). |
|
|
41
|
+
| **Best for** | Apps with heavy custom native (BLE, advanced camera, ML, payments SDKs). |
|
|
42
|
+
|
|
43
|
+
### Flutter
|
|
44
|
+
|
|
45
|
+
| | |
|
|
46
|
+
|---|---|
|
|
47
|
+
| **Pick when** | Pixel-perfect cross-platform UI with custom motion; team is Dart-OK. |
|
|
48
|
+
| **Strengths** | Skia rendering = identical UI across platforms; great motion; mature widgets. |
|
|
49
|
+
| **Costs** | Dart hiring pool smaller; web/desktop story uneven; non-trivial native interop. |
|
|
50
|
+
| **Perf ceiling** | High — Skia bypasses platform UI thread for many cases. |
|
|
51
|
+
| **Best for** | Animation-heavy or brand-precise apps; shop already on Flutter. |
|
|
52
|
+
|
|
53
|
+
### SwiftUI (native iOS)
|
|
54
|
+
|
|
55
|
+
| | |
|
|
56
|
+
|---|---|
|
|
57
|
+
| **Pick when** | iOS-only or iOS-primary; want platform-best UX and access to newest APIs day-one. |
|
|
58
|
+
| **Strengths** | Best feel, best APIs (Live Activities, WidgetKit, Vision, etc.). |
|
|
59
|
+
| **Costs** | Min iOS support shifts upward; doesn't help Android at all. |
|
|
60
|
+
| **Best for** | Premium iOS-led products; products with deep Apple-ecosystem integrations. |
|
|
61
|
+
|
|
62
|
+
### Jetpack Compose (native Android)
|
|
63
|
+
|
|
64
|
+
| | |
|
|
65
|
+
|---|---|
|
|
66
|
+
| **Pick when** | Android-only or Android-primary; want first-party Material 3 + platform integrations. |
|
|
67
|
+
| **Strengths** | Declarative + modern; Compose Multiplatform offers iOS path now. |
|
|
68
|
+
| **Costs** | Doesn't help iOS unless you commit to CMP. |
|
|
69
|
+
| **Best for** | Android-led products. |
|
|
70
|
+
|
|
71
|
+
### Compose Multiplatform (KMP + Compose)
|
|
72
|
+
|
|
73
|
+
| | |
|
|
74
|
+
|---|---|
|
|
75
|
+
| **Pick when** | Kotlin team, want shared business logic + shared UI. |
|
|
76
|
+
| **Strengths** | Code reuse high; same Kotlin stack on backend possible. |
|
|
77
|
+
| **Costs** | Newer; iOS UI parity caveats; ecosystem younger than RN/Flutter. |
|
|
78
|
+
| **Best for** | Kotlin-fluent teams comfortable with bleeding edge. |
|
|
79
|
+
|
|
80
|
+
### Capacitor / Ionic (hybrid)
|
|
81
|
+
|
|
82
|
+
| | |
|
|
83
|
+
|---|---|
|
|
84
|
+
| **Pick when** | Mostly-web app needs a thin native wrapper for store distribution. |
|
|
85
|
+
| **Strengths** | Reuse the existing web codebase 1:1; fastest path to store. |
|
|
86
|
+
| **Costs** | Performance ceiling lower than RN/Flutter; UI feel rarely passes "native". |
|
|
87
|
+
| **Best for** | Internal tools, content apps, MVPs where store presence > performance. |
|
|
88
|
+
|
|
89
|
+
### Native + KMP shared logic
|
|
90
|
+
|
|
91
|
+
| | |
|
|
92
|
+
|---|---|
|
|
93
|
+
| **Pick when** | Want native UIs (SwiftUI + Compose) + shared business logic in Kotlin. |
|
|
94
|
+
| **Strengths** | Best-of-both: native UX + shared domain. |
|
|
95
|
+
| **Costs** | Two UIs to maintain; KMP infra to manage. |
|
|
96
|
+
| **Best for** | Mature teams with enough size to maintain both fronts. |
|
|
97
|
+
|
|
98
|
+
## 3. Build & release
|
|
99
|
+
|
|
100
|
+
| Choice | Pick when |
|
|
101
|
+
|---|---|
|
|
102
|
+
| **Expo EAS** (Build, Submit, Update) | RN+Expo project, want a turn-key pipeline. |
|
|
103
|
+
| **Fastlane** | Native or RN bare, want scriptable lanes; established tool. |
|
|
104
|
+
| **Bitrise** | Cloud CI specialized for mobile, integrates with EAS/Fastlane. |
|
|
105
|
+
| **GitHub Actions + matrices** | Already on GitHub, custom needs, fewer mobile-specific shortcuts. |
|
|
106
|
+
| **App Center (sunset path)** | Legacy; migrate off. |
|
|
107
|
+
|
|
108
|
+
## 4. Auth
|
|
109
|
+
|
|
110
|
+
| Choice | Pick when |
|
|
111
|
+
|---|---|
|
|
112
|
+
| **Firebase Auth** | RN/Flutter; want SDK on every platform, simple onboarding. |
|
|
113
|
+
| **Auth0 / Clerk / WorkOS** | Enterprise SSO needs. |
|
|
114
|
+
| **Sign in with Apple / Google / Microsoft** | Required (Apple) when other social auth is offered; minimal friction. |
|
|
115
|
+
| **Custom OAuth via Supabase / your backend** | Want full control; team has experience. |
|
|
116
|
+
|
|
117
|
+
## 5. Data / sync
|
|
118
|
+
|
|
119
|
+
| Choice | Pick when |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **WatermelonDB** (RN) | Offline-first apps with large local datasets. |
|
|
122
|
+
| **Realm / Atlas Sync** | Need bi-di sync to MongoDB. |
|
|
123
|
+
| **PowerSync / Replicache / Triplit** | Modern offline + sync over Postgres / custom. |
|
|
124
|
+
| **TanStack Query + AsyncStorage / EncryptedStorage** | Most apps — fetch + cache. |
|
|
125
|
+
| **GraphQL + Apollo** | Single API for many clients (web + mobile share schema). |
|
|
126
|
+
|
|
127
|
+
## 6. State management
|
|
128
|
+
|
|
129
|
+
| Choice | Pick when |
|
|
130
|
+
|---|---|
|
|
131
|
+
| **Zustand / Jotai** | Small global state; RN |
|
|
132
|
+
| **TanStack Query** | Server-derived data dominates. |
|
|
133
|
+
| **MobX-State-Tree** | Bigger app, opinionated structure. |
|
|
134
|
+
| **Recoil** | (Niche; less active recently — verify.) |
|
|
135
|
+
| **Provider / Riverpod** | Flutter. |
|
|
136
|
+
|
|
137
|
+
## 7. Storage
|
|
138
|
+
|
|
139
|
+
- **AsyncStorage** (RN) — small KV only; not for sensitive data.
|
|
140
|
+
- **EncryptedStorage** / **react-native-keychain** — tokens, secrets.
|
|
141
|
+
- **MMKV** — fast KV; sub-ms reads; replaces AsyncStorage for perf-critical paths.
|
|
142
|
+
- **SQLite (Op-SQLite / better-sqlite)** — relational on device.
|
|
143
|
+
- **Realm** — object DB with sync.
|
|
144
|
+
|
|
145
|
+
## 8. Push notifications
|
|
146
|
+
|
|
147
|
+
| Choice | Pick when |
|
|
148
|
+
|---|---|
|
|
149
|
+
| **Expo Push** | RN/Expo; simplest path. |
|
|
150
|
+
| **OneSignal** | Cross-platform, segmentation features. |
|
|
151
|
+
| **Firebase Cloud Messaging (FCM) + APNs** | Custom backend; full control. |
|
|
152
|
+
| **Customer.io / Iterable** | Marketing-driven (campaigns, journeys). |
|
|
153
|
+
|
|
154
|
+
## 9. Analytics + observability
|
|
155
|
+
|
|
156
|
+
| Choice | Pick when |
|
|
157
|
+
|---|---|
|
|
158
|
+
| **Amplitude / Mixpanel / PostHog** | Product analytics. |
|
|
159
|
+
| **Sentry / Bugsnag** | Crash + performance. |
|
|
160
|
+
| **Firebase Performance / Crashlytics** | Free, broad coverage. |
|
|
161
|
+
| **DataDog Mobile RUM** | Already on DataDog. |
|
|
162
|
+
|
|
163
|
+
## 10. Anti-patterns Tony flags fast
|
|
164
|
+
|
|
165
|
+
- **React Native for an animation-heavy / camera-heavy app** without scoping native modules upfront.
|
|
166
|
+
- **Flutter for an iOS-primary product** when the team has no Dart history.
|
|
167
|
+
- **Capacitor for a product needing 60 fps gestures** — pick RN/native.
|
|
168
|
+
- **One framework for two products** that don't share the audience or team.
|
|
169
|
+
- **Side-loading internal apps with no signing strategy.**
|
|
170
|
+
|
|
171
|
+
## 11. Recording the choice
|
|
172
|
+
|
|
173
|
+
Tony documents the chosen stack into:
|
|
174
|
+
|
|
175
|
+
- `.wize/planning/tech-vision.md` (Fury): platforms covered, non-negotiables.
|
|
176
|
+
- `.wize/solutioning/architecture.md` (Tony): runtime, build, release pipeline, native modules required.
|
|
177
|
+
- `.wize/solutioning/adrs/ADR-002-mobile-stack.md`: trade-off log.
|
|
178
|
+
- `.wize/planning/app/device-matrix.md`: targets that justified the perf choice.
|