flow_chat 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +11 -11
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/ussd_controller.rb +9 -9
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +10 -30
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +81 -33
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +23 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/middleware.rb +25 -9
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +84 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flow_chat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Froelich
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -80,6 +80,48 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: 0.4.2
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: intercom
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '4.2'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '4.2'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: reverse_markdown
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: kramdown
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '2.4'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '2.4'
|
|
83
125
|
description: "FlowChat is a Rails framework for building sophisticated conversational
|
|
84
126
|
interfaces across USSD and WhatsApp platforms. \nCreate interactive flows with menus,
|
|
85
127
|
prompts, validation, media support, and session management. Features include \nmulti-tenancy,
|
|
@@ -92,9 +134,15 @@ extensions: []
|
|
|
92
134
|
extra_rdoc_files: []
|
|
93
135
|
files:
|
|
94
136
|
- ".DS_Store"
|
|
137
|
+
- ".cliff.toml"
|
|
95
138
|
- ".github/workflows/ci.yml"
|
|
139
|
+
- ".github/workflows/release.yml"
|
|
96
140
|
- ".gitignore"
|
|
97
141
|
- ".ruby-version"
|
|
142
|
+
- ".standard.yml"
|
|
143
|
+
- CHANGELOG.md
|
|
144
|
+
- CLAUDE.md
|
|
145
|
+
- CONTRIBUTING.md
|
|
98
146
|
- Gemfile
|
|
99
147
|
- LICENSE.txt
|
|
100
148
|
- README.md
|
|
@@ -102,71 +150,82 @@ files:
|
|
|
102
150
|
- SECURITY.md
|
|
103
151
|
- bin/console
|
|
104
152
|
- bin/setup
|
|
153
|
+
- docs/architecture.md
|
|
154
|
+
- docs/async-background-processing.md
|
|
105
155
|
- docs/configuration.md
|
|
106
|
-
- docs/
|
|
107
|
-
- docs/
|
|
108
|
-
- docs/
|
|
156
|
+
- docs/factory-pattern.md
|
|
157
|
+
- docs/gateway-context-variables.md
|
|
158
|
+
- docs/gateway-development.md
|
|
159
|
+
- docs/getting-started.md
|
|
109
160
|
- docs/instrumentation.md
|
|
110
|
-
- docs/
|
|
111
|
-
- docs/
|
|
161
|
+
- docs/platforms/telegram.md
|
|
162
|
+
- docs/platforms/ussd.md
|
|
163
|
+
- docs/platforms/whatsapp.md
|
|
112
164
|
- docs/testing.md
|
|
113
|
-
-
|
|
114
|
-
- docs/whatsapp-setup.md
|
|
165
|
+
- examples/custom_session_id_example.rb
|
|
115
166
|
- examples/http_controller.rb
|
|
167
|
+
- examples/intercom_configuration_example.rb
|
|
168
|
+
- examples/intercom_controller.rb
|
|
116
169
|
- examples/multi_tenant_whatsapp_controller.rb
|
|
117
170
|
- examples/simulator_controller.rb
|
|
118
171
|
- examples/ussd_controller.rb
|
|
119
172
|
- examples/whatsapp_controller.rb
|
|
120
173
|
- examples/whatsapp_media_examples.rb
|
|
121
|
-
- examples/whatsapp_message_job.rb
|
|
122
174
|
- flow_chat.gemspec
|
|
123
175
|
- lib/flow_chat.rb
|
|
124
|
-
- lib/flow_chat/
|
|
125
|
-
- lib/flow_chat/
|
|
126
|
-
- lib/flow_chat/base_processor.rb
|
|
176
|
+
- lib/flow_chat/app.rb
|
|
177
|
+
- lib/flow_chat/async_job.rb
|
|
127
178
|
- lib/flow_chat/config.rb
|
|
128
179
|
- lib/flow_chat/context.rb
|
|
180
|
+
- lib/flow_chat/executor.rb
|
|
181
|
+
- lib/flow_chat/factory.rb
|
|
129
182
|
- lib/flow_chat/flow.rb
|
|
130
|
-
- lib/flow_chat/
|
|
183
|
+
- lib/flow_chat/gateway_async_support.rb
|
|
184
|
+
- lib/flow_chat/generic_async_job.rb
|
|
131
185
|
- lib/flow_chat/http/gateway/simple.rb
|
|
132
|
-
- lib/flow_chat/http/middleware/executor.rb
|
|
133
|
-
- lib/flow_chat/http/processor.rb
|
|
134
186
|
- lib/flow_chat/http/renderer.rb
|
|
135
187
|
- lib/flow_chat/instrumentation.rb
|
|
136
188
|
- lib/flow_chat/instrumentation/log_subscriber.rb
|
|
137
189
|
- lib/flow_chat/instrumentation/metrics_collector.rb
|
|
138
190
|
- lib/flow_chat/instrumentation/setup.rb
|
|
191
|
+
- lib/flow_chat/intercom/client.rb
|
|
192
|
+
- lib/flow_chat/intercom/configuration.rb
|
|
193
|
+
- lib/flow_chat/intercom/gateway/intercom_api.rb
|
|
194
|
+
- lib/flow_chat/intercom/renderer.rb
|
|
139
195
|
- lib/flow_chat/interrupt.rb
|
|
140
196
|
- lib/flow_chat/phone_number_util.rb
|
|
197
|
+
- lib/flow_chat/processor.rb
|
|
141
198
|
- lib/flow_chat/prompt.rb
|
|
199
|
+
- lib/flow_chat/renderers/markdown_support.rb
|
|
142
200
|
- lib/flow_chat/session/cache_session_store.rb
|
|
143
201
|
- lib/flow_chat/session/middleware.rb
|
|
144
|
-
- lib/flow_chat/session/rails_session_store.rb
|
|
145
202
|
- lib/flow_chat/simulator/controller.rb
|
|
146
203
|
- lib/flow_chat/simulator/views/simulator.html.erb
|
|
147
|
-
- lib/flow_chat/
|
|
204
|
+
- lib/flow_chat/telegram.rb
|
|
205
|
+
- lib/flow_chat/telegram/client.rb
|
|
206
|
+
- lib/flow_chat/telegram/configuration.rb
|
|
207
|
+
- lib/flow_chat/telegram/gateway/bot_api.rb
|
|
208
|
+
- lib/flow_chat/telegram/middleware/choice_mapper.rb
|
|
209
|
+
- lib/flow_chat/telegram/renderer.rb
|
|
148
210
|
- lib/flow_chat/ussd/gateway/nalo.rb
|
|
149
|
-
- lib/flow_chat/ussd/gateway/nsano.rb
|
|
150
211
|
- lib/flow_chat/ussd/middleware/choice_mapper.rb
|
|
151
|
-
- lib/flow_chat/ussd/middleware/executor.rb
|
|
152
212
|
- lib/flow_chat/ussd/middleware/pagination.rb
|
|
153
|
-
- lib/flow_chat/ussd/processor.rb
|
|
154
213
|
- lib/flow_chat/ussd/renderer.rb
|
|
155
214
|
- lib/flow_chat/version.rb
|
|
156
|
-
- lib/flow_chat/whatsapp/app.rb
|
|
157
215
|
- lib/flow_chat/whatsapp/client.rb
|
|
158
216
|
- lib/flow_chat/whatsapp/configuration.rb
|
|
159
217
|
- lib/flow_chat/whatsapp/gateway/cloud_api.rb
|
|
160
|
-
- lib/flow_chat/whatsapp/
|
|
161
|
-
- lib/flow_chat/whatsapp/
|
|
218
|
+
- lib/flow_chat/whatsapp/id_generator.rb
|
|
219
|
+
- lib/flow_chat/whatsapp/middleware/choice_mapper.rb
|
|
162
220
|
- lib/flow_chat/whatsapp/renderer.rb
|
|
163
|
-
- lib/flow_chat/whatsapp/send_job_support.rb
|
|
164
221
|
- lib/flow_chat/whatsapp/template_manager.rb
|
|
222
|
+
- lib/tasks/release.rake
|
|
165
223
|
homepage: https://github.com/radioactive-labs/flow_chat
|
|
166
224
|
licenses:
|
|
167
225
|
- MIT
|
|
168
226
|
metadata:
|
|
169
227
|
allowed_push_host: https://rubygems.org
|
|
228
|
+
rubygems_mfa_required: 'true'
|
|
170
229
|
homepage_uri: https://github.com/radioactive-labs/flow_chat
|
|
171
230
|
source_code_uri: https://github.com/radioactive-labs/flow_chat
|
|
172
231
|
changelog_uri: https://github.com/radioactive-labs/flow_chat
|
data/docs/flows.md
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
# Flow Development Guide
|
|
2
|
-
|
|
3
|
-
This guide covers advanced flow patterns, validation techniques, and best practices for building sophisticated conversational workflows.
|
|
4
|
-
|
|
5
|
-
## Flow Architecture
|
|
6
|
-
|
|
7
|
-
### Flow Lifecycle
|
|
8
|
-
|
|
9
|
-
Every flow method must result in user interaction:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class ExampleFlow < FlowChat::Flow
|
|
13
|
-
def main_page
|
|
14
|
-
# ✅ Always end with user interaction
|
|
15
|
-
choice = app.screen(:choice) { |p| p.select "Choose:", ["A", "B"] }
|
|
16
|
-
|
|
17
|
-
case choice
|
|
18
|
-
when "A"
|
|
19
|
-
handle_option_a
|
|
20
|
-
app.say "Option A completed!" # Required interaction
|
|
21
|
-
when "B"
|
|
22
|
-
handle_option_b
|
|
23
|
-
app.say "Option B completed!" # Required interaction
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Session Management
|
|
30
|
-
|
|
31
|
-
FlowChat automatically persists screen results:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
class RegistrationFlow < FlowChat::Flow
|
|
35
|
-
def main_page
|
|
36
|
-
# These values persist across requests
|
|
37
|
-
name = app.screen(:name) { |p| p.ask "Name?" }
|
|
38
|
-
email = app.screen(:email) { |p| p.ask "Email?" }
|
|
39
|
-
|
|
40
|
-
# Show summary using cached values
|
|
41
|
-
confirmed = app.screen(:confirm) do |prompt|
|
|
42
|
-
prompt.yes? "Create account for #{name} (#{email})?"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
if confirmed
|
|
46
|
-
create_user(name: name, email: email)
|
|
47
|
-
app.say "Account created!"
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Input Validation Patterns
|
|
54
|
-
|
|
55
|
-
### Basic Validation
|
|
56
|
-
|
|
57
|
-
```ruby
|
|
58
|
-
age = app.screen(:age) do |prompt|
|
|
59
|
-
prompt.ask "Enter your age:",
|
|
60
|
-
validate: ->(input) {
|
|
61
|
-
return "Age must be a number" unless input.match?(/^\d+$/)
|
|
62
|
-
return "Must be 18 or older" unless input.to_i >= 18
|
|
63
|
-
nil # Return nil for valid input
|
|
64
|
-
},
|
|
65
|
-
transform: ->(input) { input.to_i }
|
|
66
|
-
end
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Complex Validation
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
phone = app.screen(:phone) do |prompt|
|
|
73
|
-
prompt.ask "Enter phone number:",
|
|
74
|
-
validate: ->(input) {
|
|
75
|
-
clean = input.gsub(/[\s\-\(\)]/, '')
|
|
76
|
-
return "Invalid format" unless clean.match?(/^\+?[\d]{10,15}$/)
|
|
77
|
-
return "Must start with country code" unless clean.start_with?('+')
|
|
78
|
-
nil
|
|
79
|
-
},
|
|
80
|
-
transform: ->(input) { input.gsub(/[\s\-\(\)]/, '') }
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Conditional Validation
|
|
85
|
-
|
|
86
|
-
```ruby
|
|
87
|
-
class PaymentFlow < FlowChat::Flow
|
|
88
|
-
def collect_payment_method
|
|
89
|
-
method = app.screen(:method) do |prompt|
|
|
90
|
-
prompt.select "Payment method:", ["card", "mobile_money"]
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if method == "card"
|
|
94
|
-
collect_card_details
|
|
95
|
-
else
|
|
96
|
-
collect_mobile_money_details
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
def collect_card_details
|
|
103
|
-
card = app.screen(:card) do |prompt|
|
|
104
|
-
prompt.ask "Card number (16 digits):",
|
|
105
|
-
validate: ->(input) {
|
|
106
|
-
clean = input.gsub(/\s/, '')
|
|
107
|
-
return "Must be 16 digits" unless clean.length == 16
|
|
108
|
-
return "Invalid card number" unless luhn_valid?(clean)
|
|
109
|
-
nil
|
|
110
|
-
}
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
app.say "Card ending in #{card[-4..-1]} saved."
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Menu Patterns
|
|
119
|
-
|
|
120
|
-
### Dynamic Menus
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
def show_products
|
|
124
|
-
products = fetch_available_products
|
|
125
|
-
|
|
126
|
-
choice = app.screen(:product) do |prompt|
|
|
127
|
-
prompt.select "Choose product:", products.map(&:name)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
selected_product = products.find { |p| p.name == choice }
|
|
131
|
-
show_product_details(selected_product)
|
|
132
|
-
end
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Nested Menus
|
|
136
|
-
|
|
137
|
-
```ruby
|
|
138
|
-
def main_menu
|
|
139
|
-
choice = app.screen(:main) do |prompt|
|
|
140
|
-
prompt.select "Main Menu:", {
|
|
141
|
-
"products" => "View Products",
|
|
142
|
-
"orders" => "My Orders",
|
|
143
|
-
"support" => "Customer Support"
|
|
144
|
-
}
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
case choice
|
|
148
|
-
when "products"
|
|
149
|
-
products_menu
|
|
150
|
-
when "orders"
|
|
151
|
-
orders_menu
|
|
152
|
-
when "support"
|
|
153
|
-
support_menu
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Advanced Patterns
|
|
159
|
-
|
|
160
|
-
### Multi-Step Forms
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
class CompleteProfileFlow < FlowChat::Flow
|
|
164
|
-
def main_page
|
|
165
|
-
collect_basic_info
|
|
166
|
-
collect_preferences
|
|
167
|
-
confirm_and_save
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
private
|
|
171
|
-
|
|
172
|
-
def collect_basic_info
|
|
173
|
-
app.screen(:name) { |p| p.ask "Full name:" }
|
|
174
|
-
app.screen(:email) { |p| p.ask "Email:" }
|
|
175
|
-
app.screen(:phone) { |p| p.ask "Phone:" }
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def collect_preferences
|
|
179
|
-
app.screen(:language) { |p| p.select "Language:", ["English", "French"] }
|
|
180
|
-
app.screen(:notifications) { |p| p.yes? "Enable notifications?" }
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def confirm_and_save
|
|
184
|
-
summary = build_summary
|
|
185
|
-
confirmed = app.screen(:confirm) { |p| p.yes? "Save profile?\n\n#{summary}" }
|
|
186
|
-
|
|
187
|
-
if confirmed
|
|
188
|
-
save_profile
|
|
189
|
-
app.say "Profile saved successfully!"
|
|
190
|
-
else
|
|
191
|
-
app.say "Profile not saved."
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Error Recovery
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
def process_payment
|
|
201
|
-
begin
|
|
202
|
-
amount = app.screen(:amount) do |prompt|
|
|
203
|
-
prompt.ask "Amount to pay:",
|
|
204
|
-
validate: ->(input) {
|
|
205
|
-
return "Invalid amount" unless input.match?(/^\d+(\.\d{2})?$/)
|
|
206
|
-
return "Minimum $1.00" unless input.to_f >= 1.0
|
|
207
|
-
nil
|
|
208
|
-
},
|
|
209
|
-
transform: ->(input) { input.to_f }
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
process_transaction(amount)
|
|
213
|
-
app.say "Payment of $#{amount} processed successfully!"
|
|
214
|
-
|
|
215
|
-
rescue PaymentError => e
|
|
216
|
-
app.say "Payment failed: #{e.message}. Please try again."
|
|
217
|
-
process_payment # Retry
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Cross-Platform Considerations
|
|
223
|
-
|
|
224
|
-
### Platform Detection
|
|
225
|
-
|
|
226
|
-
```ruby
|
|
227
|
-
def show_help
|
|
228
|
-
if app.context["request.gateway"] == :whatsapp_cloud_api
|
|
229
|
-
# WhatsApp users get rich media
|
|
230
|
-
app.say "Here's how to use our service:",
|
|
231
|
-
media: { type: :image, url: "https://example.com/help.jpg" }
|
|
232
|
-
else
|
|
233
|
-
# USSD users get text with link
|
|
234
|
-
app.say "Help guide: https://example.com/help"
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Progressive Enhancement
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
def collect_feedback
|
|
243
|
-
rating = app.screen(:rating) do |prompt|
|
|
244
|
-
if whatsapp?
|
|
245
|
-
# Rich interactive buttons for WhatsApp
|
|
246
|
-
prompt.select "Rate our service:", {
|
|
247
|
-
"5" => "⭐⭐⭐⭐⭐ Excellent",
|
|
248
|
-
"4" => "⭐⭐⭐⭐ Good",
|
|
249
|
-
"3" => "⭐⭐⭐ Average",
|
|
250
|
-
"2" => "⭐⭐ Poor",
|
|
251
|
-
"1" => "⭐ Very Poor"
|
|
252
|
-
}
|
|
253
|
-
else
|
|
254
|
-
# Simple numbered list for USSD
|
|
255
|
-
prompt.select "Rate our service (1-5):", ["1", "2", "3", "4", "5"]
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
app.say "Thank you for rating us #{rating} stars!"
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
private
|
|
263
|
-
|
|
264
|
-
def whatsapp?
|
|
265
|
-
app.context["request.gateway"] == :whatsapp_cloud_api
|
|
266
|
-
end
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Best Practices
|
|
270
|
-
|
|
271
|
-
### Keep Methods Focused
|
|
272
|
-
|
|
273
|
-
```ruby
|
|
274
|
-
# ✅ Good: Single responsibility
|
|
275
|
-
def collect_contact_info
|
|
276
|
-
name = app.screen(:name) { |p| p.ask "Name:" }
|
|
277
|
-
email = app.screen(:email) { |p| p.ask "Email:" }
|
|
278
|
-
{ name: name, email: email }
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# ❌ Avoid: Too much in one method
|
|
282
|
-
def handle_everything
|
|
283
|
-
# 50+ lines of mixed logic
|
|
284
|
-
end
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Use Meaningful Screen Names
|
|
288
|
-
|
|
289
|
-
```ruby
|
|
290
|
-
# ✅ Good: Descriptive names
|
|
291
|
-
app.screen(:billing_address) { |p| p.ask "Billing address:" }
|
|
292
|
-
app.screen(:confirm_payment) { |p| p.yes? "Confirm $#{amount}?" }
|
|
293
|
-
|
|
294
|
-
# ❌ Avoid: Generic names
|
|
295
|
-
app.screen(:input1) { |p| p.ask "Address:" }
|
|
296
|
-
app.screen(:confirm) { |p| p.yes? "OK?" }
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Handle Edge Cases
|
|
300
|
-
|
|
301
|
-
```ruby
|
|
302
|
-
def show_order_history
|
|
303
|
-
orders = fetch_user_orders
|
|
304
|
-
|
|
305
|
-
if orders.empty?
|
|
306
|
-
app.say "You have no previous orders."
|
|
307
|
-
return
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
choice = app.screen(:order) do |prompt|
|
|
311
|
-
prompt.select "Select order:", orders.map(&:display_name)
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
show_order_details(orders.find { |o| o.display_name == choice })
|
|
315
|
-
end
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Testing Flows
|
|
319
|
-
|
|
320
|
-
See [Testing Guide](testing.md) for comprehensive testing strategies.
|