product_tours 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +263 -113
- data/docs/screenshots/01-walkthrough.jpg +0 -0
- data/docs/screenshots/02-dashboard.jpg +0 -0
- data/docs/screenshots/03-mobile.jpg +0 -0
- data/docs/screenshots/04-editor.jpg +0 -0
- data/lib/product_tours/version.rb +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f48e747fd564905f78ca0c5f2609c0a2cef62352e8789ec4ce0fc8e9fb632dd0
|
|
4
|
+
data.tar.gz: 4dea4cbd842d0e1c3acafc5abe71719d4b882ae20750bc7441294d52e6ad7e10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca825401c59636fb1ca6f4ad5f243344e8ff83e976d2357c5b0b536336a824464c98bde51f61773d679935cb3ebe23b80cc6c22b13a6af202a19e261630e3fc5
|
|
7
|
+
data.tar.gz: ccff1b7cefeed57de45b8581c4ecd31c73a6a14d1983333dbb13bb39ee07f2c07244dff11166220d2390afa32da8b7889f2bfc0eb5b6b4feda79f2019c457f98
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.1] - 2026-08-04
|
|
6
|
+
|
|
7
|
+
- Reworked the README into an installation-first, skimmable product guide that
|
|
8
|
+
accurately documents walkthroughs, video providers, translations, demo data,
|
|
9
|
+
configuration, lifecycle events, security, and intentional non-goals.
|
|
10
|
+
- Replaced the implementation-era PRD with a concise contract for the shipped
|
|
11
|
+
product boundary and current behavior.
|
|
12
|
+
- Added real desktop dashboard, editor, walkthrough, and mobile screenshots from
|
|
13
|
+
the seeded dummy application.
|
|
14
|
+
|
|
5
15
|
## [0.1.0] - 2026-08-03
|
|
6
16
|
|
|
7
17
|
- Initial Rails engine, post dashboard, modal widget, video providers,
|
|
@@ -37,5 +47,6 @@
|
|
|
37
47
|
- Removed generic tutorial duplication now that translations provide the only
|
|
38
48
|
intentional content-copying workflow.
|
|
39
49
|
|
|
40
|
-
[Unreleased]: https://github.com/yshmarov/product_tours/compare/v0.1.
|
|
50
|
+
[Unreleased]: https://github.com/yshmarov/product_tours/compare/v0.1.1...HEAD
|
|
51
|
+
[0.1.1]: https://github.com/yshmarov/product_tours/compare/v0.1.0...v0.1.1
|
|
41
52
|
[0.1.0]: https://github.com/yshmarov/product_tours/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -1,126 +1,198 @@
|
|
|
1
1
|
# product_tours
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/product_tours)
|
|
4
|
+
[](https://rubygems.org/gems/product_tours)
|
|
3
5
|
[](https://github.com/yshmarov/product_tours/actions/workflows/ci.yml)
|
|
4
|
-
[](MIT-LICENSE)
|
|
7
|
+
[](https://github.com/yshmarov/product_tours/stargazers)
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
**Self-hosted product tours and video tutorials for Rails.** Publish one useful
|
|
10
|
+
guide, open it from any button in your app, or link several guides into a
|
|
11
|
+
Next/Back walkthrough. Your content, videos, translations, and lifecycle events
|
|
12
|
+
stay in your Rails application.
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
No SaaS account. No third-party script. No visual page builder trying to attach
|
|
15
|
+
a tooltip to a DOM node that changed last Tuesday.
|
|
16
|
+
|
|
17
|
+

|
|
11
18
|
|
|
12
19
|
## Install
|
|
13
20
|
|
|
14
21
|
```ruby
|
|
22
|
+
# Gemfile
|
|
15
23
|
gem "product_tours"
|
|
16
24
|
```
|
|
17
25
|
|
|
18
26
|
```bash
|
|
27
|
+
bundle install
|
|
19
28
|
bin/rails generate product_tours:install
|
|
20
29
|
bin/rails db:migrate
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
The migration automatically creates ready-to-use demo tutorials in development.
|
|
24
|
-
Production databases are never populated with demo content.
|
|
25
|
-
|
|
26
|
-
Add the widget before `</body>` in your application layout:
|
|
27
|
-
|
|
28
32
|
```erb
|
|
33
|
+
<%# app/views/layouts/application.html.erb, before </body> %>
|
|
29
34
|
<%= product_tours_tag %>
|
|
30
35
|
```
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
Put a trigger wherever the tutorial is useful:
|
|
33
38
|
|
|
34
39
|
```erb
|
|
35
|
-
<button
|
|
36
|
-
Watch setup guide
|
|
37
|
-
</button>
|
|
40
|
+
<button data-product-tour="billing_setup">Watch the billing guide</button>
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
Create and publish `billing_setup`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
and
|
|
43
|
+
That's it. Create and publish `billing_setup` at `/product_tours`, then click
|
|
44
|
+
your button.
|
|
45
|
+
|
|
46
|
+
The generator writes the initializer and migration, mounts the engine, and
|
|
47
|
+
prints copy-ready demo buttons. In development, the migration also creates a
|
|
48
|
+
small set of working tutorials so you can try the modal immediately.
|
|
49
|
+
|
|
50
|
+
> [!IMPORTANT]
|
|
51
|
+
> The dashboard defaults to **development only**. Set `authorize_admin` before
|
|
52
|
+
> deploying it — see [Configure](#configure).
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
Ruby >= 3.2 · Rails >= 7.1 · Active Storage only for uploaded videos · Action
|
|
55
|
+
Text only for rich descriptions.
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
compose several buttons into its own Help menu, or activate a hidden trigger
|
|
49
|
-
after page load. For example, Rails flash state and a host Stimulus controller
|
|
50
|
-
can open a guide after a redirect:
|
|
57
|
+
## What you get
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
| | |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| **Tutorials** | Title, optional rich description, optional video, one clear primary action |
|
|
62
|
+
| **Walkthroughs** | Link any tutorial to another. Next opens it in place; Back uses modal history |
|
|
63
|
+
| **Video** | YouTube, Vimeo, Loom, Tella, Voomly, direct MP4/WebM, or an upload |
|
|
64
|
+
| **Dashboard** | Published/draft tabs, key search, live preview, explicit publish controls |
|
|
65
|
+
| **Translations** | One record per language, created from the tutorial page, with locale fallback |
|
|
66
|
+
| **Demo data** | Provider examples, a complete multi-step walkthrough, draft and missing-key cases |
|
|
67
|
+
| **Events** | `viewed`, `dismissed`, `completed` through `ActiveSupport::Notifications` |
|
|
68
|
+
| **Deps** | Rails only. Plain JS — no Tailwind, Stimulus, importmap, npm, CDN, or build step |
|
|
69
|
+
| **Auth** | Lambdas over the raw request — Devise, Rails 8 auth, anything |
|
|
70
|
+
| **i18n** | 26 bundled languages, including RTL |
|
|
71
|
+
| **Turbo/CSP** | Turbo Drive, nonce-based CSP, and supported iframe origins out of the box |
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
// product_tour_autoplay_controller.js in the host app
|
|
62
|
-
connect() {
|
|
63
|
-
this.element.click()
|
|
64
|
-
}
|
|
65
|
-
```
|
|
73
|
+
## The whole flow
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
1. A developer places `data-product-tour="some_key"` where help belongs.
|
|
76
|
+
2. An admin creates that key, adds text/video, chooses Draft or Published, and
|
|
77
|
+
decides what the main button does.
|
|
78
|
+
3. A visitor clicks the host app's button. The gem resolves the current locale,
|
|
79
|
+
opens its own modal, and emits `product_tours.viewed`.
|
|
80
|
+
4. The main action closes, opens an app page, or continues to another tutorial.
|
|
81
|
+
Linked tutorials stay in the same modal and get a Back button automatically.
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
| Product-tour dashboard | Tutorial editor |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
|  |  |
|
|
86
|
+
| The default language is the canonical list. Preview, publish, translate, or edit without a deploy. | Paste a supported URL and the preview appears immediately. Choose URL or upload, then one action. |
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
locale, `draft`/`published` status,
|
|
75
|
-
optional video, optional rich description, and one primary action. Video URLs
|
|
76
|
-
support YouTube, Vimeo, Loom, Tella, Voomly, and direct MP4/WebM files.
|
|
88
|
+
<img src="docs/screenshots/03-mobile.jpg" alt="The product tutorial modal filling a mobile viewport with a video and primary action" width="390">
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
safe resolved preview. Direct videos seek to an early frame so they do not look
|
|
81
|
-
like an empty player before playback.
|
|
90
|
+
On screens up to 480px the modal becomes a full-screen sheet, respects safe
|
|
91
|
+
areas, and follows `visualViewport` while the mobile keyboard is open.
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
The New product tour button always creates that default-language record. Open a
|
|
85
|
-
tutorial to see its existing languages or add another available locale; each
|
|
86
|
-
translation remains an ordinary draft/published `Post` with the same key.
|
|
93
|
+
## Why a gem
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
| | `product_tours` | Hosted tour SaaS |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| Cost | Free, MIT | Monthly, usually tied to MAU |
|
|
98
|
+
| Where content lives | Your database | The vendor's |
|
|
99
|
+
| Trigger placement | Your Rails views and product logic | A remote visual builder |
|
|
100
|
+
| Videos | Your URLs or Active Storage | Their upload/storage rules |
|
|
101
|
+
| User/account data | Not collected by the gem | Usually synced for targeting |
|
|
102
|
+
| Analytics | Events for the tool you already use | Another dashboard |
|
|
103
|
+
| Frontend | One same-origin plain-JS file | Third-party script and network calls |
|
|
104
|
+
| If you remove it | Delete the helper and mount | Untangle remote campaigns and targeting |
|
|
92
105
|
|
|
93
|
-
|
|
106
|
+
This is intentionally the reliable half of product tours: self-contained
|
|
107
|
+
guidance modals, not DOM-anchored tooltip choreography.
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
open a page, or continue to another tutorial. Continuing opens the selected
|
|
97
|
-
tutorial in the
|
|
98
|
-
same modal and automatically gives visitors a Back button.
|
|
109
|
+
## Build a walkthrough
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
sequence model. The Back history exists only for the open modal, so a tutorial opened
|
|
102
|
-
directly never shows a misleading Back button.
|
|
111
|
+
Every tutorial has one primary action:
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
| Choice | What visitors get |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| **Finish and close** | Emits `completed` and closes the modal |
|
|
116
|
+
| **Continue to another tutorial** | Opens that key in the same modal and adds Back history |
|
|
117
|
+
| **Open a page** | Emits `completed`, then follows a relative path or HTTP(S) URL |
|
|
105
118
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
119
|
+
Select **Continue to another tutorial** in the editor and pick a tutorial in the
|
|
120
|
+
same language. Draft targets are selectable while you assemble the walkthrough;
|
|
121
|
+
publish the complete chain before exposing its first trigger.
|
|
110
122
|
|
|
111
|
-
|
|
123
|
+
There is deliberately no Course, Tour, or Step model. Tutorials remain
|
|
124
|
+
independently invokable. The modal remembers only the path the current visitor
|
|
125
|
+
took, so opening a middle tutorial directly never shows a misleading Back
|
|
126
|
+
button.
|
|
127
|
+
|
|
128
|
+
## Video
|
|
129
|
+
|
|
130
|
+
Paste any supported HTTPS URL:
|
|
131
|
+
|
|
132
|
+
| Provider | Accepted examples |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| YouTube | `youtube.com/watch`, Shorts, Live, embed, `youtu.be` |
|
|
135
|
+
| Vimeo | Public and unlisted links; privacy hashes are preserved |
|
|
136
|
+
| Loom | Share and embed links |
|
|
137
|
+
| Tella | Video links |
|
|
138
|
+
| Voomly | Share, video, and embed links |
|
|
139
|
+
| Direct | URLs ending in `.mp4` or `.webm` |
|
|
140
|
+
|
|
141
|
+
YouTube, Vimeo, and Loom metadata comes from their oEmbed endpoints. Metadata is
|
|
142
|
+
best-effort: a timeout never prevents you from saving a valid URL. YouTube uses
|
|
143
|
+
`youtube-nocookie.com`; unsupported hosts and lookalike URLs fail closed.
|
|
144
|
+
|
|
145
|
+
Direct videos seek to an early frame for a useful preview instead of showing an
|
|
146
|
+
empty player.
|
|
147
|
+
|
|
148
|
+
<details>
|
|
149
|
+
<summary><b>Upload videos or add rich descriptions</b></summary>
|
|
150
|
+
|
|
151
|
+
Both are optional Rails features:
|
|
112
152
|
|
|
113
153
|
```bash
|
|
154
|
+
bin/rails active_storage:install
|
|
114
155
|
bin/rails action_text:install
|
|
115
156
|
bin/rails db:migrate
|
|
116
157
|
```
|
|
117
158
|
|
|
118
|
-
|
|
159
|
+
Once the tables exist, the editor offers **Use a video link / Upload a video**
|
|
160
|
+
and a compact rich-text description editor. Uploaded videos are reached through
|
|
161
|
+
the engine's gated media route.
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
## Translations
|
|
166
|
+
|
|
167
|
+
The dashboard sidebar shows only `I18n.default_locale`. Open a tutorial to see
|
|
168
|
+
every existing language and add another from `I18n.available_locales`.
|
|
169
|
+
|
|
170
|
+
- New product tours always start in the default locale.
|
|
171
|
+
- A translation copies the source into a new **draft** with the same key.
|
|
172
|
+
- The key and language become locked identity; translate, review, then publish.
|
|
173
|
+
- A trigger tries `I18n.locale`, then `I18n.default_locale` only when no current-
|
|
174
|
+
locale record exists.
|
|
175
|
+
- A draft translation does **not** silently fall back to published English. It
|
|
176
|
+
stays unavailable until you publish it.
|
|
177
|
+
|
|
178
|
+
This keeps one stable developer key while giving admins an obvious place to
|
|
179
|
+
manage every language.
|
|
119
180
|
|
|
120
181
|
## Demo tutorials
|
|
121
182
|
|
|
122
|
-
|
|
123
|
-
|
|
183
|
+
Development installs seed the app's default locale automatically. Refresh the
|
|
184
|
+
full idempotent set in English, French, and Bulgarian whenever you like:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
bin/rails product_tours:seed_demo
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The demo includes every video provider, a walkthrough through all of them, a
|
|
191
|
+
direct MP4, a normal URL action, an unpublished key, and an intentionally missing
|
|
192
|
+
key. Running the task again updates those records instead of duplicating them.
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary><b>Copy-ready demo buttons</b></summary>
|
|
124
196
|
|
|
125
197
|
```erb
|
|
126
198
|
<div class="product-tours-demo">
|
|
@@ -138,48 +210,82 @@ prints into any ERB view to open every demo entry point immediately:
|
|
|
138
210
|
<%= product_tours_tag %>
|
|
139
211
|
```
|
|
140
212
|
|
|
141
|
-
|
|
142
|
-
YouTube, Vimeo, Loom, Tella, Voomly, and direct-video tutorials before its final
|
|
143
|
-
step. Every transition demonstrates the automatic Back button. The individual
|
|
144
|
-
provider buttons remain available so each video can also be opened directly.
|
|
145
|
-
The final two buttons deliberately exercise unresolved triggers: `demo_draft`
|
|
146
|
-
exists but remains unpublished, while `demo_missing_post` is never seeded.
|
|
147
|
-
|
|
148
|
-
Refresh the full idempotent demo set in English, French, and Bulgarian at any
|
|
149
|
-
time:
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
bin/rails product_tours:seed_demo
|
|
153
|
-
```
|
|
213
|
+
</details>
|
|
154
214
|
|
|
155
|
-
|
|
156
|
-
`demo_walkthrough_features`, `demo_walkthrough_finish`, and the unpublished
|
|
157
|
-
`demo_draft` in the default demo locales (`en`, `fr`, and `bg`). The missing-key
|
|
158
|
-
button intentionally has no matching record. Running the task again refreshes
|
|
159
|
-
the seeded records instead of duplicating them and prints the copy-ready block
|
|
160
|
-
again. To seed only one locale from application code, call
|
|
161
|
-
`ProductTours::Seeds.load!(locale: :fr)`.
|
|
215
|
+
## Configure
|
|
162
216
|
|
|
163
|
-
|
|
217
|
+
Everything is optional — a development install works with zero config. In
|
|
218
|
+
`config/initializers/product_tours.rb`:
|
|
164
219
|
|
|
165
|
-
|
|
220
|
+
| Option | Default | What it does |
|
|
221
|
+
| --- | --- | --- |
|
|
222
|
+
| `enabled` | everyone | Who can resolve and open published tutorials |
|
|
223
|
+
| `authorize_admin` | development only | **Who can manage content at the mount path** |
|
|
224
|
+
| `admin_layout` | gem layout | Render the dashboard inside your admin shell |
|
|
225
|
+
| `storage_service` | app default | Named Active Storage service for uploaded videos |
|
|
226
|
+
| `mount_path` | `/product_tours` | Keep in sync only when mounting the engine manually |
|
|
166
227
|
|
|
167
228
|
```ruby
|
|
168
229
|
ProductTours.configure do |config|
|
|
230
|
+
config.enabled = ->(request) { request.env["warden"]&.user.present? }
|
|
169
231
|
config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
232
|
+
config.admin_layout = "admin/application"
|
|
233
|
+
config.storage_service = :product_tours
|
|
170
234
|
end
|
|
171
235
|
```
|
|
172
236
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
Gates receive the **raw request**, so Devise, Rails 8 authentication, Flipper,
|
|
238
|
+
or your own session model all work without an adapter.
|
|
239
|
+
|
|
240
|
+
## Trigger it from your own UI
|
|
241
|
+
|
|
242
|
+
The gem ships no floating launcher. A trigger belongs in the navigation,
|
|
243
|
+
settings card, empty state, or success screen where it makes sense:
|
|
244
|
+
|
|
245
|
+
```erb
|
|
246
|
+
<a href="#" data-product-tour="invite_team">How team invitations work</a>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Keep `<%= product_tours_tag %>` in the layout when triggers appear across the
|
|
250
|
+
app. When `enabled` returns false, the helper renders nothing and the endpoints
|
|
251
|
+
also reject the request.
|
|
252
|
+
|
|
253
|
+
<details>
|
|
254
|
+
<summary><b>Open a tutorial after a redirect</b></summary>
|
|
255
|
+
|
|
256
|
+
The host owns timing. Rails flash plus a tiny Stimulus controller is enough:
|
|
257
|
+
|
|
258
|
+
```erb
|
|
259
|
+
<% if flash[:product_tour].present? %>
|
|
260
|
+
<button hidden
|
|
261
|
+
data-controller="product-tour-autoplay"
|
|
262
|
+
data-product-tour="<%= flash[:product_tour] %>"></button>
|
|
263
|
+
<% end %>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
// product_tour_autoplay_controller.js in your app
|
|
268
|
+
connect() {
|
|
269
|
+
this.element.click()
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
</details>
|
|
274
|
+
|
|
275
|
+
## Lifecycle events
|
|
176
276
|
|
|
177
|
-
|
|
277
|
+
The gem persists no analytics, user identity, cookies, progress, or completion
|
|
278
|
+
table. It emits three Rails notifications:
|
|
178
279
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
280
|
+
| Event | Meaning |
|
|
281
|
+
| --- | --- |
|
|
282
|
+
| `product_tours.viewed` | The modal became visible and focused |
|
|
283
|
+
| `product_tours.dismissed` | The visitor closed it before using the primary action |
|
|
284
|
+
| `product_tours.completed` | The visitor used the primary action |
|
|
285
|
+
|
|
286
|
+
Payload: `post_id`, `key`, `locale`, query-free `page_url`, and `source`.
|
|
287
|
+
|
|
288
|
+
Bridge them to Ahoy—or anything else—in your host app:
|
|
183
289
|
|
|
184
290
|
```ruby
|
|
185
291
|
ActiveSupport::Notifications.subscribe(/^product_tours\./) do |name, _start, _finish, _id, payload|
|
|
@@ -187,23 +293,67 @@ ActiveSupport::Notifications.subscribe(/^product_tours\./) do |name, _start, _fi
|
|
|
187
293
|
end
|
|
188
294
|
```
|
|
189
295
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
296
|
+
Your subscriber can attach `Current.user` or account context. That identity does
|
|
297
|
+
not need to become product-tour configuration.
|
|
298
|
+
|
|
299
|
+
## Broken triggers fail loudly, not publicly
|
|
300
|
+
|
|
301
|
+
`data-product-tour="key"` is a contract between code and dashboard content.
|
|
302
|
+
Invalid, missing, draft, or disabled keys open nothing for the visitor.
|
|
303
|
+
|
|
304
|
+
- Development/test: raises `ProductTours::UnresolvedTriggerError`.
|
|
305
|
+
- Production: reports through `Rails.error`, logs as a fallback, and emits
|
|
306
|
+
`product_tours.unresolved_trigger` with `invalid_key`, `missing`,
|
|
307
|
+
`unpublished`, or `disabled`.
|
|
193
308
|
|
|
194
|
-
|
|
309
|
+
No end user gets a Rails error page because somebody renamed a tutorial.
|
|
195
310
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
311
|
+
## Security
|
|
312
|
+
|
|
313
|
+
- Admin authorization runs server-side on every dashboard request.
|
|
314
|
+
- Public resolution returns only published tutorials allowed by `enabled`.
|
|
315
|
+
- Video providers are an HTTPS allowlist; unsupported URLs render no iframe.
|
|
316
|
+
- Action URLs accept relative app paths or HTTP(S), never script schemes.
|
|
317
|
+
- Widget/dashboard assets are same-origin and fingerprinted.
|
|
318
|
+
- Rails CSP nonces are preserved. Supported provider origins are merged into
|
|
319
|
+
`frame-src` without replacing the host policy.
|
|
320
|
+
- For direct videos on another origin, allow that origin in the host app's
|
|
321
|
+
`media-src` policy.
|
|
322
|
+
|
|
323
|
+
## What it doesn't do
|
|
324
|
+
|
|
325
|
+
No anchored tooltips, selector recorder, page-rule engine, automatic scheduler,
|
|
326
|
+
checklists, persisted progress, analytics dashboard, resource center, AI writer,
|
|
327
|
+
Segment/Mixpanel/Slack integration, or user/account sync.
|
|
328
|
+
|
|
329
|
+
The host app owns trigger timing and Help navigation. Ahoy or your analytics
|
|
330
|
+
stack owns persistence and reporting. The gem stays small enough to understand.
|
|
200
331
|
|
|
201
332
|
## Development
|
|
202
333
|
|
|
203
334
|
```bash
|
|
204
|
-
bundle install
|
|
205
335
|
bundle exec rake test
|
|
206
336
|
bundle exec rubocop
|
|
207
337
|
```
|
|
208
338
|
|
|
209
|
-
Rails 7.1
|
|
339
|
+
CI runs Rails 7.1 / 7.2 / 8.0 / 8.1 against Ruby 3.2 / 3.3 / 3.4.
|
|
340
|
+
|
|
341
|
+
Bug reports and pull requests are welcome. The most useful report is a real
|
|
342
|
+
Rails app and the exact point where installation or authoring felt confusing.
|
|
343
|
+
|
|
344
|
+
## Also by the same author
|
|
345
|
+
|
|
346
|
+
- [testimonials](https://github.com/yshmarov/testimonials) — testimonials,
|
|
347
|
+
video reviews, and NPS for Rails.
|
|
348
|
+
- [livechat](https://github.com/yshmarov/livechat) — in-app support messaging
|
|
349
|
+
for Rails.
|
|
350
|
+
- [ideasbugs](https://github.com/yshmarov/ideasbugs) — in-app bug reports and
|
|
351
|
+
feature requests.
|
|
352
|
+
- [i18n_proofreading](https://github.com/yshmarov/i18n_proofreading) — in-context
|
|
353
|
+
translation proofreading.
|
|
354
|
+
- [SupeRails](https://superails.com) — Rails screencasts.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT. If it saved you a subscription, a
|
|
359
|
+
[⭐](https://github.com/yshmarov/product_tours) is a fair trade.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: product_tours
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rails
|
|
@@ -83,6 +82,10 @@ files:
|
|
|
83
82
|
- config/locales/product_tours.vi.yml
|
|
84
83
|
- config/locales/product_tours.zh-CN.yml
|
|
85
84
|
- config/routes.rb
|
|
85
|
+
- docs/screenshots/01-walkthrough.jpg
|
|
86
|
+
- docs/screenshots/02-dashboard.jpg
|
|
87
|
+
- docs/screenshots/03-mobile.jpg
|
|
88
|
+
- docs/screenshots/04-editor.jpg
|
|
86
89
|
- lib/generators/product_tours/install/install_generator.rb
|
|
87
90
|
- lib/generators/product_tours/install/templates/create_product_tours_posts.rb.tt
|
|
88
91
|
- lib/generators/product_tours/install/templates/initializer.rb
|
|
@@ -109,7 +112,6 @@ metadata:
|
|
|
109
112
|
changelog_uri: https://github.com/yshmarov/product_tours/blob/main/CHANGELOG.md
|
|
110
113
|
bug_tracker_uri: https://github.com/yshmarov/product_tours/issues
|
|
111
114
|
rubygems_mfa_required: 'true'
|
|
112
|
-
post_install_message:
|
|
113
115
|
rdoc_options: []
|
|
114
116
|
require_paths:
|
|
115
117
|
- lib
|
|
@@ -124,8 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
124
126
|
- !ruby/object:Gem::Version
|
|
125
127
|
version: '0'
|
|
126
128
|
requirements: []
|
|
127
|
-
rubygems_version: 3.
|
|
128
|
-
signing_key:
|
|
129
|
+
rubygems_version: 3.6.9
|
|
129
130
|
specification_version: 4
|
|
130
131
|
summary: Self-hosted product tours and video tutorials for Rails apps.
|
|
131
132
|
test_files: []
|