@mhosaic/feedback-cli 0.14.1 → 0.17.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.
- package/dist/bin.js +3 -3
- package/dist/{doctor-HTTAR4ZB.js → doctor-K2C6LBR2.js} +2 -2
- package/dist/doctor-K2C6LBR2.js.map +1 -0
- package/dist/{init-P45CEXDP.js → init-RS7BKALE.js} +3 -3
- package/dist/init-RS7BKALE.js.map +1 -0
- package/dist/{install-skill-3OPVFMTK.js → install-skill-QJ4ZDVVR.js} +4 -2
- package/dist/{install-skill-3OPVFMTK.js.map → install-skill-QJ4ZDVVR.js.map} +1 -1
- package/package.json +1 -1
- package/skills/feedback-close/SKILL.md +58 -0
- package/skills/feedback-fix/SKILL.md +121 -0
- package/skills/feedback-pull/SKILL.md +56 -0
- package/skills/feedback-watch-merges/SKILL.md +66 -0
- package/skills/integrate-feedback/SKILL.md +100 -75
- package/skills/integrate-feedback/references/consumer-install-astro.md +26 -6
- package/skills/integrate-feedback/references/consumer-install-next.md +39 -14
- package/skills/integrate-feedback/references/consumer-install-nuxt.md +35 -12
- package/skills/integrate-feedback/references/consumer-install-plain.md +55 -30
- package/skills/integrate-feedback/references/consumer-install-remix.md +32 -7
- package/skills/integrate-feedback/references/consumer-install-svelte.md +27 -7
- package/skills/integrate-feedback/references/consumer-install-vite.md +23 -14
- package/skills/integrate-feedback/references/consumer-install-vue.md +34 -11
- package/skills/integrate-feedback/references/consumer-install.md +74 -27
- package/skills/integrate-feedback/references/identify-snippets.md +3 -3
- package/skills/integrate-feedback/references/operator-provision.md +125 -33
- package/skills/issue-pull/SKILL.md +46 -0
- package/dist/doctor-HTTAR4ZB.js.map +0 -1
- package/dist/init-P45CEXDP.js.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# Consumer install — plain HTML / CDN (no bundler)
|
|
2
2
|
|
|
3
|
-
For host apps without a JS bundler (Django templates, Rails ERB, Hugo, plain `.html` files served statically, etc.),
|
|
3
|
+
For host apps without a JS bundler (Django templates, Rails ERB, Hugo, plain `.html` files served statically, etc.), drop in the CDN-hosted loader. No npm install needed.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The loader is the **manifest-driven thin client** — it's tiny (~4 KB gzipped) and never changes. The actual widget bundle is fetched at runtime from a Mhosaic-controlled manifest URL, so once you've installed the tag below, **you never edit it again** — Mhosaic publishes a new release row and your next page load picks up the new widget bundle.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Step 1 — Insert one script tag, forever
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
12
|
<script
|
|
13
|
-
src="https://cdn.jsdelivr.net/npm/@mhosaic/feedback
|
|
14
|
-
integrity="sha384-<
|
|
13
|
+
src="https://cdn.jsdelivr.net/npm/@mhosaic/feedback@1/dist/loader.min.js"
|
|
14
|
+
integrity="sha384-<LOADER_SRI_HASH>"
|
|
15
15
|
crossorigin="anonymous"
|
|
16
16
|
data-key="pk_proj_…"
|
|
17
17
|
data-endpoint="https://software-factory-3tbbu.ondigitalocean.app"
|
|
@@ -20,30 +20,34 @@ The bundle is published to jsDelivr from the npm release. Get the current versio
|
|
|
20
20
|
></script>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
- **Version pin (`@1`):** pin to the LOADER's major version, not a patch version. The loader's API is stable within v1; we'll never break it. Patch updates to the loader propagate via jsDelivr's caches automatically.
|
|
24
|
+
- **SRI:** the loader file is tiny and changes rarely. Get the current SRI from the latest GitHub Release notes for `feedback-tool-mhosaic` (or compute with `curl -s https://cdn.jsdelivr.net/npm/@mhosaic/feedback@1/dist/loader.min.js | openssl dgst -sha384 -binary | openssl base64 -A`).
|
|
25
|
+
- **The widget bundle SRI is NOT in this snippet.** That hash is delivered to the loader at runtime via the manifest endpoint, and the loader injects the widget bundle with its own (current) SRI on the fly. So the bundle is always integrity-checked, but the host never has to update its SRI when the widget version changes.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Drop the `<script>` tag near the closing `</body>` of every page that should show the FAB. For Django: `templates/base.html`. For Rails: `app/views/layouts/application.html.erb`. For static sites: the page template / partial that's shared across pages.
|
|
27
|
+
Drop the tag near the closing `</body>` of every page that should show the FAB. For Django: `templates/base.html`. For Rails: `app/views/layouts/application.html.erb`. For static sites: the page template / partial that's shared across pages.
|
|
30
28
|
|
|
31
29
|
If your app has a Content Security Policy, you'll need to allow:
|
|
32
|
-
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
30
|
+
|
|
31
|
+
- `script-src https://cdn.jsdelivr.net` (for both the loader and the widget bundle)
|
|
32
|
+
- `connect-src https://software-factory-3tbbu.ondigitalocean.app` (for the manifest endpoint + report submissions)
|
|
33
|
+
- `worker-src 'blob:'` (only if the operator-enabled replay module is in use)
|
|
35
34
|
|
|
36
35
|
---
|
|
37
36
|
|
|
38
|
-
## Step
|
|
37
|
+
## Step 2 — Wire identify() via the queue API
|
|
39
38
|
|
|
40
|
-
The
|
|
39
|
+
The loader exposes a global `window.Feedback` queue function. Drop this **before** the `<script src=…>` tag so the queue exists when the loader initializes:
|
|
41
40
|
|
|
42
41
|
```html
|
|
43
42
|
<script>
|
|
44
|
-
window.Feedback =
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
window.Feedback =
|
|
44
|
+
window.Feedback ||
|
|
45
|
+
function () {
|
|
46
|
+
;(window.Feedback.q = window.Feedback.q || []).push({
|
|
47
|
+
name: arguments[0],
|
|
48
|
+
args: Array.prototype.slice.call(arguments, 1),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
47
51
|
</script>
|
|
48
52
|
```
|
|
49
53
|
|
|
@@ -65,20 +69,40 @@ For anonymous-only deployments, pass a stable placeholder id so the FAB still re
|
|
|
65
69
|
|
|
66
70
|
```html
|
|
67
71
|
<script>
|
|
68
|
-
|
|
72
|
+
var anonId =
|
|
73
|
+
localStorage.getItem('anonId') ||
|
|
74
|
+
(function () {
|
|
75
|
+
var id = 'anon-' + crypto.randomUUID()
|
|
76
|
+
localStorage.setItem('anonId', id)
|
|
77
|
+
return id
|
|
78
|
+
})()
|
|
79
|
+
Feedback('identify', { id: anonId, name: 'Anonymous' })
|
|
69
80
|
</script>
|
|
70
81
|
```
|
|
71
82
|
|
|
72
83
|
---
|
|
73
84
|
|
|
85
|
+
## How updates work after install
|
|
86
|
+
|
|
87
|
+
Mhosaic ships a new widget release (new bundle URL + SRI in the Release table). The next time any user loads any page on your site, the loader:
|
|
88
|
+
|
|
89
|
+
1. Fetches `<endpoint>/api/feedback/v1/widget-manifest/?pk=<your_pk>` (with a 60-second cache header so subsequent page loads in the same minute don't re-fetch).
|
|
90
|
+
2. Reads the response: `{version, bundle_url, sri_hash}`.
|
|
91
|
+
3. Injects `<script src="<bundle_url>" integrity="<sri_hash>" crossorigin="anonymous">` — the browser refuses to run the bundle if its bytes don't match.
|
|
92
|
+
4. Widget mounts. Done.
|
|
93
|
+
|
|
94
|
+
No host action required. No PRs to merge. No SRI hash to update. The manifest endpoint is the trust root (it's on your Mhosaic backend, served over HTTPS), and SRI on the injected bundle protects against jsDelivr compromise between the manifest lookup and the script load.
|
|
95
|
+
|
|
96
|
+
If Mhosaic ever needs to disable the widget on your project (kill switch), they flip `Project.widget_enabled = False` in the admin — the next page load gets `{enabled: false}` and the loader silently bails. Your site doesn't crash.
|
|
97
|
+
|
|
98
|
+
If you need to **pin to a specific widget version** (e.g. before a critical launch), the operator sets `Project.pinned_version = <release>` — your site stays on that exact version regardless of new releases until they unset the pin.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
74
102
|
## Gotchas
|
|
75
103
|
|
|
76
|
-
- **No auto-capture modules
|
|
77
|
-
- **
|
|
78
|
-
```bash
|
|
79
|
-
curl -s https://cdn.jsdelivr.net/npm/@mhosaic/feedback@<VERSION>/dist/embed.min.js | openssl dgst -sha384 -binary | openssl base64 -A
|
|
80
|
-
```
|
|
81
|
-
- **CDN cache lag:** new versions can take up to an hour to propagate across jsDelivr's edge. If you just published and the version 404s, wait or use `https://cdn.jsdelivr.net/npm/@mhosaic/feedback/dist/embed.min.js` (without `@VERSION`, but breaks SRI) for a one-off test, then pin properly.
|
|
104
|
+
- **No auto-capture modules in the CDN install.** `withErrorTracking` / `withReplay` / `withWebVitals` are middleware wrappers that ship via the npm path — and on that path **error-tracking is now on by default** (uncaught errors + unhandled rejections auto-file as synthetic reports). The CDN `<script>` install is the bare manual-submit FAB only; it has no error-tracking. If the client wants automatic error capture, they have to switch to the bundled (npm) install — drop the `<script>` tag and follow the framework-specific reference instead.
|
|
105
|
+
- **CDN cache lag for new LOADER releases:** new loader patch versions can take up to an hour to propagate across jsDelivr's edge. Bundle updates (the actual widget code) are NOT affected by this — they're pinned with a unique URL per version, so caching is per-version.
|
|
82
106
|
|
|
83
107
|
---
|
|
84
108
|
|
|
@@ -86,7 +110,8 @@ For anonymous-only deployments, pass a stable placeholder id so the FAB still re
|
|
|
86
110
|
|
|
87
111
|
The `verify` CLI command reads `.env.local`, which the CDN install doesn't use. To verify the CDN install, drive Chrome to your page and check:
|
|
88
112
|
|
|
89
|
-
1. The `<script>` tag loads (DevTools network → no 404 / SRI error)
|
|
113
|
+
1. The loader `<script>` tag loads (DevTools network → no 404 / SRI error)
|
|
90
114
|
2. `window.Feedback` exists (DevTools console: `typeof Feedback`)
|
|
91
|
-
3. The
|
|
92
|
-
4.
|
|
115
|
+
3. The bundle gets fetched (DevTools network → a second request to jsDelivr for `widget.min.js`)
|
|
116
|
+
4. The FAB renders (visual check; bottom-right corner)
|
|
117
|
+
5. Submitting a test report lands in `/reports` (see `references/verify-install.md`)
|
|
@@ -17,16 +17,14 @@ FEEDBACK_ENDPOINT=https://software-factory-3tbbu.ondigitalocean.app
|
|
|
17
17
|
|
|
18
18
|
## Step 2 — Expose env via the root loader
|
|
19
19
|
|
|
20
|
-
In `app/root.tsx`, add a loader that surfaces the values to the client window:
|
|
20
|
+
In `app/root.tsx`, add a loader that surfaces the values to the client window. Error-tracking is **on by default** — the mount wraps `withErrorTracking` so uncaught errors + unhandled rejections auto-file as synthetic reports:
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
23
|
import type { LoaderFunctionArgs } from '@remix-run/node'
|
|
24
24
|
import { json, useLoaderData, Outlet, Scripts } from '@remix-run/react'
|
|
25
25
|
import { useEffect } from 'react'
|
|
26
|
-
import { createFeedback } from '@mhosaic/feedback'
|
|
26
|
+
import { createFeedback } from '@mhosaic/feedback/loader'
|
|
27
27
|
import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
|
|
28
|
-
import { withReplay } from '@mhosaic/feedback/replay'
|
|
29
|
-
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
30
28
|
|
|
31
29
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
32
30
|
return json({
|
|
@@ -42,11 +40,11 @@ export default function App() {
|
|
|
42
40
|
useEffect(() => {
|
|
43
41
|
const { FEEDBACK_API_KEY, FEEDBACK_ENDPOINT } = data.ENV
|
|
44
42
|
if (!FEEDBACK_API_KEY || !FEEDBACK_ENDPOINT) return
|
|
45
|
-
withErrorTracking(
|
|
43
|
+
withErrorTracking(createFeedback({
|
|
46
44
|
apiKey: FEEDBACK_API_KEY,
|
|
47
45
|
endpoint: FEEDBACK_ENDPOINT,
|
|
48
46
|
env: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
|
|
49
|
-
}))
|
|
47
|
+
}))
|
|
50
48
|
}, [data.ENV])
|
|
51
49
|
|
|
52
50
|
return (
|
|
@@ -66,7 +64,34 @@ export default function App() {
|
|
|
66
64
|
}
|
|
67
65
|
```
|
|
68
66
|
|
|
69
|
-
The `window.ENV` script tag is Remix's idiomatic env-passing pattern. If your app already has a root loader doing this, just add the two
|
|
67
|
+
The `window.ENV` script tag is Remix's idiomatic env-passing pattern. If your app already has a root loader doing this, just add the two FEEDBACK\_\* keys to the existing `ENV` object instead of duplicating the loader.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Optional — add web-vitals / replay
|
|
72
|
+
|
|
73
|
+
`withWebVitals` and `withReplay` stay **opt-in** (heavier / more sensitive). Add them by chaining around the default instance, error-tracking innermost so it arms first. Import the extra wrappers and replace the `withErrorTracking(createFeedback(...))` call in the `useEffect` above with:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { withReplay } from '@mhosaic/feedback/replay'
|
|
77
|
+
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
78
|
+
|
|
79
|
+
withReplay(
|
|
80
|
+
withWebVitals(
|
|
81
|
+
withErrorTracking(
|
|
82
|
+
createFeedback({
|
|
83
|
+
apiKey: FEEDBACK_API_KEY,
|
|
84
|
+
endpoint: FEEDBACK_ENDPOINT,
|
|
85
|
+
env: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
|
|
86
|
+
}),
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Replay (rrweb, ~30 KiB gzip) captures DOM — confirm the privacy posture before enabling it.
|
|
93
|
+
|
|
94
|
+
**Opting out of error-tracking** (collect-on-consent posture): omit the `withErrorTracking` wrap and pass the bare `createFeedback(...)` instance. Note error-tracking POSTs error messages + stack traces — fine for most apps, but flag it if your error strings can carry end-user PII.
|
|
70
95
|
|
|
71
96
|
---
|
|
72
97
|
|
|
@@ -19,16 +19,14 @@ SvelteKit uses Vite's env system but with the `PUBLIC_` prefix for client-readab
|
|
|
19
19
|
|
|
20
20
|
## Step 2 — Mount in `+layout.svelte`
|
|
21
21
|
|
|
22
|
-
Edit `src/routes/+layout.svelte
|
|
22
|
+
Edit `src/routes/+layout.svelte`. Error-tracking is **on by default** — the mount wraps `withErrorTracking` so uncaught errors + unhandled rejections auto-file as synthetic reports:
|
|
23
23
|
|
|
24
24
|
```svelte
|
|
25
25
|
<script lang="ts">
|
|
26
26
|
import { onMount } from 'svelte'
|
|
27
27
|
import { env } from '$env/dynamic/public'
|
|
28
|
-
import { createFeedback } from '@mhosaic/feedback'
|
|
28
|
+
import { createFeedback } from '@mhosaic/feedback/loader'
|
|
29
29
|
import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
|
|
30
|
-
import { withReplay } from '@mhosaic/feedback/replay'
|
|
31
|
-
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
32
30
|
|
|
33
31
|
let fb: ReturnType<typeof createFeedback> | undefined
|
|
34
32
|
|
|
@@ -37,9 +35,7 @@ Edit `src/routes/+layout.svelte`:
|
|
|
37
35
|
const endpoint = env.PUBLIC_FEEDBACK_ENDPOINT
|
|
38
36
|
if (!apiKey || !endpoint) return
|
|
39
37
|
fb = withErrorTracking(
|
|
40
|
-
|
|
41
|
-
withWebVitals(createFeedback({ apiKey, endpoint, env: import.meta.env.PROD ? 'prod' : 'dev' }))
|
|
42
|
-
)
|
|
38
|
+
createFeedback({ apiKey, endpoint, env: import.meta.env.PROD ? 'prod' : 'dev' })
|
|
43
39
|
)
|
|
44
40
|
})
|
|
45
41
|
</script>
|
|
@@ -51,6 +47,30 @@ Edit `src/routes/+layout.svelte`:
|
|
|
51
47
|
|
|
52
48
|
---
|
|
53
49
|
|
|
50
|
+
## Optional — add web-vitals / replay
|
|
51
|
+
|
|
52
|
+
`withWebVitals` and `withReplay` stay **opt-in** (heavier / more sensitive). Add them by chaining around the default instance, error-tracking innermost so it arms first:
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import { withReplay } from '@mhosaic/feedback/replay'
|
|
57
|
+
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
58
|
+
|
|
59
|
+
// inside onMount, replacing the default wrap:
|
|
60
|
+
fb = withReplay(
|
|
61
|
+
withWebVitals(
|
|
62
|
+
withErrorTracking(createFeedback({ apiKey, endpoint, env: import.meta.env.PROD ? 'prod' : 'dev' }))
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
</script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Replay (rrweb, ~30 KiB gzip) captures DOM — confirm the privacy posture before enabling it.
|
|
69
|
+
|
|
70
|
+
**Opting out of error-tracking** (collect-on-consent posture): omit the `withErrorTracking` wrap and assign the bare `createFeedback(...)` instance. Note error-tracking POSTs error messages + stack traces — fine for most apps, but flag it if your error strings can carry end-user PII.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
54
74
|
## Step 3 — Wire identify()
|
|
55
75
|
|
|
56
76
|
If your app uses SvelteKit's `+layout.server.ts` to return the user, surface that to the client via the layout `data` and watch it with a `$:` block:
|
|
@@ -7,6 +7,7 @@ The CLI auto-wires this configuration. Your job is to **verify** the auto-wrap l
|
|
|
7
7
|
## After `mhosaic-feedback init` ran
|
|
8
8
|
|
|
9
9
|
The CLI should have:
|
|
10
|
+
|
|
10
11
|
1. Installed `@mhosaic/feedback`
|
|
11
12
|
2. Written `.env.local` with `VITE_FEEDBACK_API_KEY=…` and `VITE_FEEDBACK_ENDPOINT=…`
|
|
12
13
|
3. Wrapped `src/main.tsx` with `<FeedbackProvider>` between the marker comments
|
|
@@ -15,7 +16,7 @@ Read `src/main.tsx` and confirm you see:
|
|
|
15
16
|
|
|
16
17
|
```typescript
|
|
17
18
|
// === mhosaic-feedback:import:start ===
|
|
18
|
-
import { FeedbackProvider } from '@mhosaic/feedback/react'
|
|
19
|
+
import { FeedbackProvider } from '@mhosaic/feedback/loader/react'
|
|
19
20
|
// === mhosaic-feedback:import:end ===
|
|
20
21
|
|
|
21
22
|
// ... existing imports
|
|
@@ -39,37 +40,45 @@ If any of those markers is missing, the auto-wrap failed (rare — usually becau
|
|
|
39
40
|
|
|
40
41
|
---
|
|
41
42
|
|
|
42
|
-
##
|
|
43
|
+
## Auto-capture modules
|
|
44
|
+
|
|
45
|
+
`<FeedbackProvider>` arms **error-tracking by default** — the `errorTracking` prop defaults to `true`, so uncaught errors + unhandled rejections auto-file as synthetic reports with nothing extra to wire. Opt out (collect-on-consent posture) with:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<FeedbackProvider apiKey={apiKey} endpoint={endpoint} errorTracking={false}>
|
|
49
|
+
```
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
To also add **web-vitals / replay** (still opt-in, heavier), drop the provider's auto-wrap and build the instance manually in a small client-only module:
|
|
45
52
|
|
|
46
53
|
```typescript
|
|
47
54
|
// src/feedback.ts
|
|
48
|
-
import { createFeedback } from '@mhosaic/feedback'
|
|
55
|
+
import { createFeedback } from '@mhosaic/feedback/loader'
|
|
49
56
|
import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
|
|
50
57
|
import { withReplay } from '@mhosaic/feedback/replay'
|
|
51
58
|
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
52
59
|
|
|
53
|
-
export const fb =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
export const fb = withReplay(
|
|
61
|
+
withWebVitals(
|
|
62
|
+
withErrorTracking(
|
|
63
|
+
createFeedback({
|
|
64
|
+
apiKey: import.meta.env.VITE_FEEDBACK_API_KEY,
|
|
65
|
+
endpoint: import.meta.env.VITE_FEEDBACK_ENDPOINT,
|
|
66
|
+
env: import.meta.env.PROD ? 'prod' : 'dev',
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
61
70
|
)
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
Then in `src/main.tsx` replace the `<FeedbackProvider>` wrap with an import of `./feedback` at the top — so the side effect (widget mount + auto-capture) runs once at module load:
|
|
65
74
|
|
|
66
75
|
```typescript
|
|
67
|
-
import './feedback'
|
|
76
|
+
import './feedback' // mounts the widget + arms error/replay/webvitals
|
|
68
77
|
```
|
|
69
78
|
|
|
70
79
|
And remove the `<FeedbackProvider>` JSX wrap. The widget mounts itself to its own Shadow DOM container; there's no React provider needed unless you want to call `useFeedback()` from components.
|
|
71
80
|
|
|
72
|
-
If you do want `useFeedback()`, keep `<FeedbackProvider>`
|
|
81
|
+
If you do want `useFeedback()`, keep `<FeedbackProvider>` _and_ the manual `createFeedback()` — they cooperate. See `packages/core/src/react/FeedbackProvider.tsx` for the contract.
|
|
73
82
|
|
|
74
83
|
---
|
|
75
84
|
|
|
@@ -17,25 +17,21 @@ VITE_FEEDBACK_ENDPOINT=https://software-factory-3tbbu.ondigitalocean.app
|
|
|
17
17
|
|
|
18
18
|
## Step 2 — Initialize at app boot
|
|
19
19
|
|
|
20
|
-
Edit `src/main.ts
|
|
20
|
+
Edit `src/main.ts`. Error-tracking is **on by default** — the boot wraps `withErrorTracking` so uncaught errors + unhandled rejections auto-file as synthetic reports:
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
23
|
import { createApp } from 'vue'
|
|
24
24
|
import App from './App.vue'
|
|
25
25
|
|
|
26
|
-
import { createFeedback } from '@mhosaic/feedback'
|
|
26
|
+
import { createFeedback } from '@mhosaic/feedback/loader'
|
|
27
27
|
import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
|
|
28
|
-
import { withReplay } from '@mhosaic/feedback/replay'
|
|
29
|
-
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
30
28
|
|
|
31
29
|
const fb = withErrorTracking(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}))
|
|
38
|
-
)
|
|
30
|
+
createFeedback({
|
|
31
|
+
apiKey: import.meta.env.VITE_FEEDBACK_API_KEY,
|
|
32
|
+
endpoint: import.meta.env.VITE_FEEDBACK_ENDPOINT,
|
|
33
|
+
env: import.meta.env.PROD ? 'prod' : 'dev',
|
|
34
|
+
}),
|
|
39
35
|
)
|
|
40
36
|
|
|
41
37
|
const app = createApp(App)
|
|
@@ -51,6 +47,33 @@ The widget mounts itself to its own Shadow DOM container outside Vue's root. Cal
|
|
|
51
47
|
|
|
52
48
|
---
|
|
53
49
|
|
|
50
|
+
## Optional — add web-vitals / replay
|
|
51
|
+
|
|
52
|
+
`withWebVitals` and `withReplay` stay **opt-in** (heavier / more sensitive). Add them by chaining around the default instance, error-tracking innermost so it arms first:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { withReplay } from '@mhosaic/feedback/replay'
|
|
56
|
+
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
57
|
+
|
|
58
|
+
const fb = withReplay(
|
|
59
|
+
withWebVitals(
|
|
60
|
+
withErrorTracking(
|
|
61
|
+
createFeedback({
|
|
62
|
+
apiKey: import.meta.env.VITE_FEEDBACK_API_KEY,
|
|
63
|
+
endpoint: import.meta.env.VITE_FEEDBACK_ENDPOINT,
|
|
64
|
+
env: import.meta.env.PROD ? 'prod' : 'dev',
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Replay (rrweb, ~30 KiB gzip) captures DOM — confirm the privacy posture before enabling it.
|
|
72
|
+
|
|
73
|
+
**Opting out of error-tracking** (collect-on-consent posture): omit the `withErrorTracking` wrap and assign the bare `createFeedback(...)` instance. Note error-tracking POSTs error messages + stack traces — fine for most apps, but flag it if your error strings can carry end-user PII.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
54
77
|
## Step 3 — Wire identify()
|
|
55
78
|
|
|
56
79
|
Use the Supabase / Firebase / custom JWT recipe from `references/identify-snippets.md`. Vue 3's `watchEffect` is the idiomatic place to call `fb.identify()` from a Pinia/Vuex store or a composable:
|
|
@@ -4,6 +4,36 @@ You are guiding a teammate through installing the @mhosaic/feedback widget into
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## CRITICAL: API key handling — read this before doing anything
|
|
8
|
+
|
|
9
|
+
The `pk_proj_…` public widget key is **NOT safe to commit to git**. Even though it's a "public" key (it ends up in the browser-side bundle at runtime), committing it is wrong for several concrete reasons:
|
|
10
|
+
|
|
11
|
+
- Git history is permanent. If the key needs rotation (compromise, audit finding, project transfer), you can't actually remove it — every clone of the repo still has it.
|
|
12
|
+
- GitHub's secret scanners flag any `pk_proj_…` literal in a public repo and notify the org. Even private repos can leak via accidental fork or contributor offboarding.
|
|
13
|
+
- The key is tied to a specific project's `allowed_origins` allow-list — if it leaks and gets used from a domain the operator forgot to lock down, you get garbage reports from the wild.
|
|
14
|
+
|
|
15
|
+
**Rules. NO exceptions.**
|
|
16
|
+
|
|
17
|
+
1. **NEVER commit the API key to any tracked file.** Not `.env`, not `.env.example`, not `config.ts`, not a comment, not a README. Not even "temporarily, I'll rotate later."
|
|
18
|
+
2. **If the user asks "can I commit this?" — the answer is NO.** Don't rationalize "it's public anyway" or "it's bundled into the JS at build time so what's the harm." Both are true; neither matters. Refer them to this section.
|
|
19
|
+
3. **If the host's deploy pipeline excludes `.env.local`** (common with Docker builds — Dockerfile `COPY` of `frontend/` typically skips `.env*.local` via `.dockerignore`, or the build context only includes specific files), the key MUST come from **deploy-platform env vars** (DO App Platform > component > Environment Variables; Vercel > Project Settings > Env Vars; etc.), NOT from a tracked file. Pause and tell the user. Do NOT switch to `.env` (which IS tracked) as a workaround.
|
|
20
|
+
4. **`.env.local` (or framework equivalent) must be in `.gitignore`.** The CLI's `init` command verifies this. If you're editing files manually because the CLI failed, you verify it yourself before writing the key anywhere.
|
|
21
|
+
5. **The endpoint URL (`https://software-factory-…`) IS safe to commit** — it's a public URL with no auth on its surface. Only the API key matters.
|
|
22
|
+
|
|
23
|
+
**Per-environment-variable destination table** (which file gets the key for which scenario):
|
|
24
|
+
|
|
25
|
+
| Scenario | API key goes in | Tracked by git? |
|
|
26
|
+
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------- |
|
|
27
|
+
| Local dev, Vite/Next/Nuxt/etc. without Docker | `.env.local` | No (gitignored) |
|
|
28
|
+
| Local dev with Docker Compose (host-side build) | `.env.local` | No |
|
|
29
|
+
| Production deploy (DO App Platform, Vercel, Netlify, etc.) | Platform env vars UI | No (lives only on the platform) |
|
|
30
|
+
| Docker build where `.env.local` is excluded from context | Platform env vars (passed in at build time via `--build-arg` or runtime injection) | No |
|
|
31
|
+
| CI build for staging/prod | CI secret (GH Actions secrets, etc.), surfaced as env var to the build | No |
|
|
32
|
+
|
|
33
|
+
If a scenario isn't in the table and you're unsure, **stop and ask the operator**. Do not improvise toward a tracked file.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
7
37
|
## Step 1 — Parse the handoff payload
|
|
8
38
|
|
|
9
39
|
Ask the user to paste the handoff payload as plain Markdown. Extract:
|
|
@@ -19,7 +49,17 @@ If the user can't find the payload, walk them through asking the operator for it
|
|
|
19
49
|
|
|
20
50
|
## Step 2 — Classify the host app
|
|
21
51
|
|
|
22
|
-
`
|
|
52
|
+
Before anything else, **confirm the cwd is actually the target.** This is the most common operator footgun: someone runs `/integrate-feedback` inside `feedback-tool-mhosaic` (where the skill is conveniently invokable) and the consumer phase silently writes the widget into our own monorepo. Two cheap checks:
|
|
53
|
+
|
|
54
|
+
- Read `package.json`. If its `name` is `@mhosaic/feedback-tool-mhosaic`, `@mhosaic/feedback`, `@mhosaic/feedback-admin`, `@mhosaic/feedback-cli`, or contains the literal string `mhosaic/feedback`: **STOP**. The user is inside our own monorepo, not their host app. Tell them:
|
|
55
|
+
|
|
56
|
+
> "Looks like we're inside `feedback-tool-mhosaic` itself. The consumer install writes to the current directory; running it here would install the widget into our own monorepo. Either pass `--cwd <target-app-path>` to the CLI, or close Claude and re-open it inside the target app's root, then re-run `/integrate-feedback` and pick 'Install (consumer)'."
|
|
57
|
+
|
|
58
|
+
Wait for the user to confirm they've moved before continuing.
|
|
59
|
+
|
|
60
|
+
- If `package.json` is missing AND the cwd contains `packages/cli/skills/integrate-feedback/` (we're inside the skill's own source), same stop-and-redirect.
|
|
61
|
+
|
|
62
|
+
Now `Read` the user's `package.json`. Three branches:
|
|
23
63
|
|
|
24
64
|
1. **package.json exists with `dependencies`** → continue to Step 3 (framework detection).
|
|
25
65
|
2. **No package.json, but `index.html` / `templates/` / `app/views/` / similar template directory exists** → this is a non-JS host app (Django, Rails, Hugo, plain HTML/static site, etc.). Skip Steps 3-5 and go directly to **`references/consumer-install-plain.md`** (CDN `<script>` flow). Tell the user: "I see this isn't a JS/TS app — the CDN path is the right one. The npm-package + framework-snippet flow doesn't apply."
|
|
@@ -29,22 +69,22 @@ If the user can't find the payload, walk them through asking the operator for it
|
|
|
29
69
|
|
|
30
70
|
## Step 3 — Detect the framework
|
|
31
71
|
|
|
32
|
-
The CLI does framework detection itself, but you should know the result
|
|
72
|
+
The CLI does framework detection itself, but you should know the result _before_ running it so you can decide which post-install snippet (if any) the user needs to apply.
|
|
33
73
|
|
|
34
74
|
Read `package.json`'s `dependencies` + `devDependencies` and classify:
|
|
35
75
|
|
|
36
|
-
| Found
|
|
37
|
-
|
|
38
|
-
| `vite` + `react` + `@vitejs/plugin-react` | `vite-react`
|
|
39
|
-
| `next`
|
|
40
|
-
| `nuxt`
|
|
41
|
-
| `astro`
|
|
42
|
-
| `@remix-run/react`
|
|
43
|
-
| `vue` ≥ 3 (no `nuxt`)
|
|
44
|
-
| `@sveltejs/kit`
|
|
45
|
-
| none of the above
|
|
76
|
+
| Found | Framework | Reference doc | CLI auto-wires entry? |
|
|
77
|
+
| ----------------------------------------- | --------------------------------------------------------- | ---------------------------- | --------------------- |
|
|
78
|
+
| `vite` + `react` + `@vitejs/plugin-react` | `vite-react` | `consumer-install-vite.md` | ✅ yes |
|
|
79
|
+
| `next` | `nextjs` (App or Pages Router — sniff `app/` vs `pages/`) | `consumer-install-next.md` | ❌ paste snippet |
|
|
80
|
+
| `nuxt` | `nuxt` | `consumer-install-nuxt.md` | ❌ paste snippet |
|
|
81
|
+
| `astro` | `astro` | `consumer-install-astro.md` | ❌ paste snippet |
|
|
82
|
+
| `@remix-run/react` | `remix` | `consumer-install-remix.md` | ❌ paste snippet |
|
|
83
|
+
| `vue` ≥ 3 (no `nuxt`) | `vue` | `consumer-install-vue.md` | ❌ paste snippet |
|
|
84
|
+
| `@sveltejs/kit` | `sveltekit` | `consumer-install-svelte.md` | ❌ paste snippet |
|
|
85
|
+
| none of the above | `plain` / `unknown` | `consumer-install-plain.md` | ❌ CDN snippet |
|
|
46
86
|
|
|
47
|
-
Order matters: check for `nuxt`
|
|
87
|
+
Order matters: check for `nuxt` _before_ generic `vue`, since Nuxt apps have both. Same for `astro` vs the underlying renderer (Astro apps can also have React/Vue/Svelte).
|
|
48
88
|
|
|
49
89
|
Tell the user which framework you detected. Confirm with `AskUserQuestion` (especially if Next.js, since App vs Pages Router branches differently).
|
|
50
90
|
|
|
@@ -62,12 +102,14 @@ npx @mhosaic/feedback-cli@latest init \
|
|
|
62
102
|
```
|
|
63
103
|
|
|
64
104
|
This will:
|
|
105
|
+
|
|
65
106
|
- `npm install @mhosaic/feedback` (using the project's package manager)
|
|
66
107
|
- Write `.env.local` with `VITE_FEEDBACK_API_KEY=…` + `VITE_FEEDBACK_ENDPOINT=…` (Vite-flavored names; rename per framework if Next/Vue/etc. — see framework references)
|
|
67
108
|
- Ensure `.env.local` is in `.gitignore`
|
|
68
109
|
- If `vite-react`: auto-wrap `src/main.tsx` with `<FeedbackProvider>`
|
|
69
110
|
|
|
70
111
|
Surface the CLI's output verbatim. If install fails, stop and read the error message; common causes:
|
|
112
|
+
|
|
71
113
|
- No internet
|
|
72
114
|
- Lockfile conflict (offer to delete `pnpm-lock.yaml` / `yarn.lock` / `package-lock.json` and retry)
|
|
73
115
|
- Wrong directory (no `package.json`)
|
|
@@ -86,6 +128,7 @@ Open the per-framework reference document and follow it verbatim:
|
|
|
86
128
|
- Plain HTML / CDN: `consumer-install-plain.md`
|
|
87
129
|
|
|
88
130
|
Each reference doc gives:
|
|
131
|
+
|
|
89
132
|
1. The env-var prefix (e.g. `NEXT_PUBLIC_FEEDBACK_API_KEY` for Next.js — the CLI's default `VITE_FEEDBACK_*` needs renaming)
|
|
90
133
|
2. The entry-point snippet to paste (with import + provider/init call)
|
|
91
134
|
3. Where to put it (exact file path)
|
|
@@ -103,31 +146,32 @@ If you can't detect the auth provider from `package.json`, ask the user explicit
|
|
|
103
146
|
|
|
104
147
|
---
|
|
105
148
|
|
|
106
|
-
## Step 7 —
|
|
149
|
+
## Step 7 — Auto-capture modules (error-tracking is on by default)
|
|
150
|
+
|
|
151
|
+
**Error-tracking ships on by default.** `<FeedbackProvider>` arms it automatically (the `errorTracking` prop defaults to `true`), and every non-React snippet in this skill wraps `withErrorTracking` by default. It auto-files synthetic reports for `window.onerror` + `unhandledrejection` — this is the telemetry that feeds the platform's frontend-error Issue scan, so **leave it on** unless the consumer has a specific reason not to. There's nothing to wire for the default case.
|
|
152
|
+
|
|
153
|
+
Two heavier modules stay opt-in (more overhead / more sensitive data):
|
|
107
154
|
|
|
108
155
|
`AskUserQuestion`:
|
|
109
|
-
|
|
156
|
+
|
|
157
|
+
- question: `Add the heavier auto-capture modules? (error-tracking is already on)`
|
|
110
158
|
- options:
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
159
|
+
- `No — error-tracking only (recommended)`, description: `Keep the default. Smallest footprint.`
|
|
160
|
+
- `Add web-vitals`, description: `Core Web Vitals (LCP/INP/CLS) as telemetry. Small.`
|
|
161
|
+
- `Add web-vitals + replay`, description: `Also session replay (rrweb), ~30 KiB gzip — captures DOM, confirm the privacy posture first.`
|
|
114
162
|
|
|
115
|
-
|
|
163
|
+
To add the opt-in modules, chain their wrappers around the instance (error-tracking innermost so it arms first):
|
|
116
164
|
|
|
117
165
|
```typescript
|
|
118
|
-
import { createFeedback } from '@mhosaic/feedback'
|
|
166
|
+
import { createFeedback } from '@mhosaic/feedback/loader'
|
|
119
167
|
import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
|
|
120
168
|
import { withReplay } from '@mhosaic/feedback/replay'
|
|
121
169
|
import { withWebVitals } from '@mhosaic/feedback/webvitals'
|
|
122
170
|
|
|
123
|
-
const fb = withErrorTracking(
|
|
124
|
-
withReplay(
|
|
125
|
-
withWebVitals(createFeedback({ apiKey, endpoint }))
|
|
126
|
-
)
|
|
127
|
-
)
|
|
171
|
+
const fb = withReplay(withWebVitals(withErrorTracking(createFeedback({ apiKey, endpoint }))))
|
|
128
172
|
```
|
|
129
173
|
|
|
130
|
-
|
|
174
|
+
**Opting out of error-tracking** (collect-on-consent posture): pass `<FeedbackProvider errorTracking={false}>`, or on the `createFeedback` path simply omit the `withErrorTracking` wrap. Note it POSTs error messages + stack traces — fine for the vast majority of apps, but flag it if the consumer's error strings can carry end-user PII.
|
|
131
175
|
|
|
132
176
|
---
|
|
133
177
|
|
|
@@ -140,12 +184,14 @@ npx @mhosaic/feedback-cli@latest verify \
|
|
|
140
184
|
```
|
|
141
185
|
|
|
142
186
|
This is a lightweight check; it confirms:
|
|
187
|
+
|
|
143
188
|
- `.env.local` has the right shape
|
|
144
189
|
- The endpoint resolves and responds
|
|
145
190
|
- CORS allows the consumer's origin
|
|
146
191
|
- The public key is accepted (POSTs a `[mhosaic-feedback verify]` synthetic report)
|
|
147
192
|
|
|
148
193
|
If anything is red, the verify command prints a hint. Common hints:
|
|
194
|
+
|
|
149
195
|
- `add this origin to the project's allowed_origins (admin SPA: Edit project)` → operator needs to add the dev/prod origin.
|
|
150
196
|
- `key invalid or revoked` → operator needs to mint a new key.
|
|
151
197
|
|
|
@@ -174,10 +220,11 @@ Summarize for the user:
|
|
|
174
220
|
- Widget installed ✓
|
|
175
221
|
- FAB renders ✓
|
|
176
222
|
- Smoke-test report received ✓
|
|
177
|
-
-
|
|
223
|
+
- Error-tracking: on by default ✓ (or `errorTracking={false}` if the consumer opted out) · extra modules wired: [web-vitals / replay / none]
|
|
178
224
|
- Next: deploy to staging, add the staging URL to `allowed_origins` (operator action — DM them the URL)
|
|
179
225
|
|
|
180
226
|
Encourage them to:
|
|
227
|
+
|
|
181
228
|
- Commit the change (sans `.env.local`)
|
|
182
229
|
- Tell the operator the smoke test passed
|
|
183
230
|
- Delete the smoke-test reports from `/reports` in admin once it lands
|
|
@@ -14,7 +14,7 @@ The widget's FAB is hidden until `fb.identify()` is called with a non-empty `id`
|
|
|
14
14
|
|
|
15
15
|
import { useEffect } from 'react'
|
|
16
16
|
import { useAuth0 } from '@auth0/auth0-react'
|
|
17
|
-
import { useFeedback } from '@mhosaic/feedback/react'
|
|
17
|
+
import { useFeedback } from '@mhosaic/feedback/loader/react'
|
|
18
18
|
|
|
19
19
|
export function FeedbackIdentity() {
|
|
20
20
|
const { user, isAuthenticated, isLoading } = useAuth0()
|
|
@@ -40,7 +40,7 @@ Mount `<FeedbackIdentity />` once inside both `<Auth0Provider>` and `<FeedbackPr
|
|
|
40
40
|
```tsx
|
|
41
41
|
import { useEffect } from 'react'
|
|
42
42
|
import { useUser } from '@clerk/clerk-react'
|
|
43
|
-
import { useFeedback } from '@mhosaic/feedback/react'
|
|
43
|
+
import { useFeedback } from '@mhosaic/feedback/loader/react'
|
|
44
44
|
|
|
45
45
|
export function FeedbackIdentity() {
|
|
46
46
|
const { isLoaded, isSignedIn, user } = useUser()
|
|
@@ -118,7 +118,7 @@ Place after `initializeApp()` and `createFeedback()`. The callback fires immedia
|
|
|
118
118
|
|
|
119
119
|
import { useEffect } from 'react'
|
|
120
120
|
import { useSession } from 'next-auth/react'
|
|
121
|
-
import { useFeedback } from '@mhosaic/feedback/react'
|
|
121
|
+
import { useFeedback } from '@mhosaic/feedback/loader/react'
|
|
122
122
|
|
|
123
123
|
export function FeedbackIdentity() {
|
|
124
124
|
const { data: session, status } = useSession()
|