@mhosaic/feedback-cli 0.14.0 → 0.16.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.
Files changed (27) hide show
  1. package/dist/bin.js +4 -4
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{doctor-HTTAR4ZB.js → doctor-K2C6LBR2.js} +2 -2
  4. package/dist/doctor-K2C6LBR2.js.map +1 -0
  5. package/dist/{init-64QSAHMB.js → init-RS7BKALE.js} +14 -3
  6. package/dist/init-RS7BKALE.js.map +1 -0
  7. package/dist/{install-skill-CIZ3ZQI7.js → install-skill-QJ4ZDVVR.js} +8 -5
  8. package/dist/{install-skill-CIZ3ZQI7.js.map → install-skill-QJ4ZDVVR.js.map} +1 -1
  9. package/package.json +1 -1
  10. package/skills/feedback-close/SKILL.md +58 -0
  11. package/skills/feedback-fix/SKILL.md +107 -0
  12. package/skills/feedback-pull/SKILL.md +56 -0
  13. package/skills/feedback-watch-merges/SKILL.md +62 -0
  14. package/skills/integrate-feedback/SKILL.md +109 -64
  15. package/skills/integrate-feedback/references/consumer-install-astro.md +83 -0
  16. package/skills/integrate-feedback/references/consumer-install-next.md +2 -2
  17. package/skills/integrate-feedback/references/consumer-install-nuxt.md +108 -0
  18. package/skills/integrate-feedback/references/consumer-install-plain.md +55 -30
  19. package/skills/integrate-feedback/references/consumer-install-remix.md +1 -1
  20. package/skills/integrate-feedback/references/consumer-install-svelte.md +1 -1
  21. package/skills/integrate-feedback/references/consumer-install-vite.md +2 -2
  22. package/skills/integrate-feedback/references/consumer-install-vue.md +1 -1
  23. package/skills/integrate-feedback/references/consumer-install.md +67 -18
  24. package/skills/integrate-feedback/references/identify-snippets.md +3 -3
  25. package/skills/integrate-feedback/references/operator-provision.md +77 -33
  26. package/dist/doctor-HTTAR4ZB.js.map +0 -1
  27. package/dist/init-64QSAHMB.js.map +0 -1
@@ -1,99 +1,138 @@
1
1
  ---
2
2
  name: integrate-feedback
3
- description: Walk a teammate through integrating @mhosaic/feedback either provisioning a project as the platform operator, or installing the widget in a host app as the consumer. Use when the user mentions adding the Mhosaic feedback widget, onboarding a new app, getting a public key, or wiring the widget into their codebase.
3
+ description: Full guide for integrating @mhosaic/feedback. Covers operator-side provisioning (Company/Project/pk_proj_ key creation via the admin SPA), consumer-side install (CLI run + framework-specific wiring + identify() recipes + smoke test), and every common question a teammate might ask — what permissions are needed, where to run from, how the operator→consumer handoff works, what Chrome MCP is for, external clients vs internal apps, why the FAB isn't appearing, CORS errors, etc. Use whenever the user mentions: the Mhosaic feedback widget, adding feedback to an app, onboarding a new client/app, getting a pk_proj_ key, deploying for a company, configuring identify(), CORS errors on the widget, or any troubleshooting around the integration. Equally useful as a slash-command runbook and as a reference Claude reads to answer ad-hoc questions.
4
+ user-invocable: true
4
5
  ---
5
6
 
6
7
  # /integrate-feedback — guided widget integration
7
8
 
8
- You are guiding a teammate through integrating the **@mhosaic/feedback** widget into a project. The workflow splits into two modes:
9
+ End-to-end flow for getting the **@mhosaic/feedback** widget into a host app. There are two phases, run in two separate places:
9
10
 
10
- - **Operator mode** — the platform admin (Victor or a delegate). Provisions a new Company / Project / public key on the Mhosaic backend. Has admin access to the software-factory admin SPA.
11
- - **Consumer mode** — a teammate. Installs the widget in their own app, given a key + endpoint from the operator.
11
+ 1. **Operator phase** — provisions Company / Project / `pk_proj_…` key on the Mhosaic backend (https://software-factory-3tbbu.ondigitalocean.app). Run from **inside `feedback-tool-mhosaic`**. The skill drives Chrome at the admin SPA, no filesystem changes. At the end, it outputs a Markdown handoff payload and offers to install itself globally so phase 2 is invokable in the client's repo.
12
12
 
13
- **Both modes are guided. Never assume confirm at every checkpoint.**
13
+ 2. **Consumer phase** installs the widget in the host app. Run from **inside the client's app directory** (NOT feedback-tool-mhosaic). The skill detects the framework, runs the CLI's `init`, pastes the right entry-point snippet, wires `identify()` against the client's auth provider, and runs a live smoke test in Chrome.
14
+
15
+ This skill is **both** a slash-command runbook AND a reference doc. When the user invokes `/integrate-feedback`, run the procedural flow. When they just ask an integration question, answer from the Q&A below.
14
16
 
15
17
  ---
16
18
 
17
- ## Step 0: Identify the mode
19
+ ## Quick Q&A
18
20
 
19
- Ask the user which mode they're in. Use `AskUserQuestion` with:
21
+ **"What does this skill do?"**
22
+ End-to-end widget integration. Operator phase: creates Company/Project/key on the backend. Consumer phase: installs the widget in a host app + runs a smoke test that confirms a report lands in admin. Framework-aware (Vite/Next/Nuxt/Astro/Remix/Vue/SvelteKit/plain HTML). Auth-aware (Auth0/Clerk/Supabase/Firebase/NextAuth/Django/JWT/anonymous).
20
23
 
21
- - question: `Which side of the integration are you working on?`
22
- - header: `Mode`
23
- - options:
24
- - label: `Provision (operator)`, description: `Create a new Company/Project/key in the Mhosaic backend. Requires admin SPA access.`
25
- - label: `Install (consumer)`, description: `Add the widget to a host app. You already have a pk_proj_… key + endpoint from the operator.`
26
- - label: `Both — start with operator, then consumer`, description: `Provision first, then keep going.`
24
+ **"Do I need to install anything first?"**
27
25
 
28
- Branch on the answer. Each branch is documented in detail under `references/`.
26
+ - Inside `feedback-tool-mhosaic`: no. The skill is committed under `.claude/skills/integrate-feedback/`; `/integrate-feedback` works directly.
27
+ - In the client's repo: optional. If you want `/integrate-feedback` invokable there too (recommended for the consumer phase), the operator phase will offer to install it globally for you (one shell command, `npx @mhosaic/feedback-cli@latest install-skill`). Skip the install and the consumer phase can still happen — you just run `npx @mhosaic/feedback-cli@latest init …` directly in the client's repo and follow the framework-specific snippet from `references/`.
29
28
 
30
- ---
29
+ **"Where do I run `/integrate-feedback` from?"**
30
+
31
+ - Operator phase: **inside `feedback-tool-mhosaic`** (any directory in the monorepo).
32
+ - Consumer phase: **inside the client's app's project root**.
33
+ - Don't run consumer phase inside `feedback-tool-mhosaic` — the CLI would install the widget into our own monorepo. The skill detects this (via `package.json` name check) and refuses.
31
34
 
32
- ## Operator mode provisioning
35
+ **"What permissions do I need on the backend?"**
33
36
 
34
- **Goal:** end-to-end, produce a Markdown "handoff payload" the teammate can paste into Claude Code (or follow manually) to install the widget.
37
+ - Login: Feedback Django group.
38
+ - Create a Project/key under an existing company: Feedback group + Membership for that company.
39
+ - Create a brand-new Company (external client): `is_staff=True` + `is_superuser=True`. Held by the platform maintainers.
35
40
 
36
- **Read** the full step-by-step procedure at:
41
+ **"What's Chrome MCP and why do I need it?"**
42
+ A Claude Code MCP server that lets the skill drive a real Chrome tab. Operator phase uses it to call our admin API from the authenticated browser session (no token paste). Consumer phase uses it for the smoke test. If MCP isn't configured, operator phase stops at Step 0.5 with an install hint; consumer phase degrades to "open the dev URL yourself and confirm visually."
37
43
 
38
- - `references/operator-provision.md`
44
+ **"How does the handoff between phases work?"**
45
+ End of operator phase produces a Markdown payload (endpoint, `pk_proj_…`, slug, allowed origins). The skill prints it AND offers to install itself globally. The operator then closes this Claude session, `cd`s into the client's repo, opens a fresh `claude`, runs `/integrate-feedback`, picks "Install (consumer)", and pastes the payload as the first message.
39
46
 
40
- The procedure walks through:
47
+ **"My FAB isn't appearing — what's wrong?"**
48
+ The FAB stays hidden until `fb.identify({id: 'something'})` runs with a non-empty `id`. Check `references/identify-snippets.md` for your auth provider's recipe. For testing, hardcode `fb.identify({id: 'test'})` to force-render.
41
49
 
42
- 1. Confirming the company exists (or creating it)
43
- 2. Collecting the project name, slug, and `allowed_origins` (the teammate's deployment URL[s])
44
- 3. Driving Chrome MCP to the admin SPA at https://software-factory-3tbbu.ondigitalocean.app
45
- 4. Calling the DRF API from the *already-authenticated* browser context (no token copy-paste)
46
- 5. Minting a `pk_proj_…` widget key and capturing its plaintext value
47
- 6. Assembling and printing the handoff payload
50
+ **"CORS errors / 403 / origin not allowed."**
51
+ The project's `allowed_origins` doesn't include the URL the request comes from. Usual causes: forgot the dev URL (e.g. `http://localhost:5173`), trailing slash, path included, wildcard (rejected by design). Operator opens admin SPA → Edit project → adds the missing origin.
48
52
 
49
- **Crucial:** the operator must already be SSO-logged-in to software-factory before the skill drives Chrome. If they're not, the skill detects this and walks them through `/login` first.
53
+ **"External client vs internal Mhosaic app?"**
50
54
 
51
- After the operator branch finishes, ask whether they want to continue into consumer mode in the same conversation (helpful when they're integrating into one of their own apps).
55
+ - Internal: use existing companies (`mhosaic`, `arime`, `norag`); create a new project under one. Needs Feedback group + Membership.
56
+ - External: create a brand-new Company. Needs `is_staff=True`. The client can't log into software-factory (it's @mhosaic.com-only SSO); Mhosaic acts as report-viewer for v1.
57
+
58
+ **"Do clients get email digests?"**
59
+ Not by default — notification dispatcher is `NoopDispatcher` on prod. Ping the operator if the client wants the weekly digest (it's a backend env var change).
60
+
61
+ **"Where's the human-readable doc?"**
62
+ `docs/INTEGRATING.md` in this repo. Covers the skill, the manual fallback, the CLI command reference, and a troubleshooting matrix.
52
63
 
53
64
  ---
54
65
 
55
- ## Consumer modeinstalling
66
+ ## Step 0Identify the phase
67
+
68
+ `AskUserQuestion`:
56
69
 
57
- **Goal:** end-to-end, the widget loads on the host app and a smoke-test report lands in the admin.
70
+ - question: `Which phase are you running?`
71
+ - header: `Phase`
72
+ - options:
73
+ - label: `Provision (operator)`, description: `Create a new Company/Project/key on the Mhosaic backend. Run this from inside feedback-tool-mhosaic.`
74
+ - label: `Install (consumer)`, description: `Install the widget into a host app. Run this from inside the client's app directory, with the handoff payload from the operator phase.`
75
+
76
+ Branch on the answer.
77
+
78
+ ---
58
79
 
59
- **Read** the framework-aware install procedure at:
80
+ ## Step 0.5 Pre-flight
60
81
 
61
- - `references/consumer-install.md`
82
+ **For operator phase:** confirm Chrome MCP is configured. Call `mcp__claude-in-chrome__tabs_context_mcp` once. If the tool isn't available, tell the user:
62
83
 
63
- The procedure walks through:
84
+ > "Operator phase needs Chrome MCP. Two options:
85
+ >
86
+ > 1. Install Chrome MCP (Claude Code → MCP settings → add the chrome connector), then re-run `/integrate-feedback`.
87
+ > 2. Provision manually via `docs/INTEGRATING.md` (open the admin SPA, create the project, mint the key)."
64
88
 
65
- 1. Verifying we're in a JS/TS project (reads `package.json`)
66
- 2. Detecting the framework (Vite/Next/Remix/Vue/SvelteKit/CDN)
67
- 3. Accepting the handoff payload — either pasted verbatim or via `--from-clipboard`
68
- 4. Running `npx @mhosaic/feedback-cli init --api-key=… --endpoint=… --yes` (non-interactive)
69
- 5. For non-Vite-React frameworks: pasting the framework-specific snippet from `references/consumer-install-<framework>.md` and asking the user to apply it
70
- 6. Wiring `fb.identify()` against the consumer's auth provider — recipes in `references/identify-snippets.md`
71
- 7. Optionally wiring `withErrorTracking` + `withReplay` + `withWebVitals`
72
- 8. Running `mhosaic-feedback verify --origin <consumer dev origin>` to confirm CORS, key, and endpoint
73
- 9. Starting the consumer's dev server and driving Chrome to verify the FAB renders
74
- 10. Submitting a `[INTEGRATE-FEEDBACK SMOKE TEST]` synthetic report from the FAB
75
- 11. Driving Chrome to software-factory's `/reports` and confirming the smoke-test report appears
89
+ Stop. Don't continue without MCP.
76
90
 
77
- The smoke-test workflow lives at:
91
+ **For consumer phase:** read `package.json` in the cwd. If its `name` is `@mhosaic/feedback-tool-mhosaic` or any of our own monorepo packages, the user is inside our own repo — refuse and redirect:
78
92
 
79
- - `references/verify-install.md`
93
+ > "Looks like we're inside `feedback-tool-mhosaic`. The consumer phase writes to the current directory; running it here would install the widget into our own monorepo. Close Claude and re-open in the client app's root, then run `/integrate-feedback` again."
94
+
95
+ Stop. Don't continue.
80
96
 
81
97
  ---
82
98
 
83
- ## Principles to follow
99
+ ## Operator phase
84
100
 
85
- - **Confirm at every fork.** Never assume a default. Use `AskUserQuestion` for choices, plain text questions for free-form input.
86
- - **Show, don't tell.** When you can run a command and show output, run it. When you can drive Chrome and screenshot, drive Chrome.
87
- - **Idempotent and reversible.** Every state-changing step has a stated rollback (e.g. revoke key in admin SPA, run `mhosaic-feedback eject`). Surface that to the user.
88
- - **Stop on first red.** If the smoke test fails, do not silently retry. Read `references/verify-install.md`'s diagnostic table, narrow the cause, fix it, then re-run from the failing step.
89
- - **Don't paste secret keys into git.** Walk the user through adding `.env.local` to `.gitignore` (CLI does this automatically; double-check).
90
- - **Don't claim "done" without proof.** End the workflow with a screenshot of the smoke-test report landing in admin OR with a clearly enumerated red checklist of what's still broken.
101
+ Read **`references/operator-provision.md`** and follow it step by step:
102
+
103
+ 1. Confirm operator is SSO-logged-in to software-factory in Chrome.
104
+ 2. Collect: company (existing or new), project name + slug, allowed origins, `share_reports_with_widget`.
105
+ 3. Drive Chrome MCP to the admin SPA, refresh the JWT via `/api/auth/token/refresh/`, call `POST /companies/`, `POST /projects/`, `POST /project-keys/create/` from the in-tab `fetch()` (uses the existing cookies).
106
+ 4. Capture the plaintext `pk_proj_…` from the keys response.
107
+ 5. Verify the project + key show up in the admin SPA.
108
+ 6. Print the Markdown handoff payload (format below).
109
+ 7. Offer to install the skill globally so the operator can run `/integrate-feedback` in the client's repo for the consumer phase. The full offer logic is at the end of `references/operator-provision.md`.
110
+
111
+ After this phase the operator has: (a) the handoff payload on their clipboard / in the chat, (b) optionally `/integrate-feedback` available globally so the next `claude` in the client repo picks it up.
112
+
113
+ ---
114
+
115
+ ## Consumer phase
116
+
117
+ Read **`references/consumer-install.md`** and follow it step by step:
118
+
119
+ 1. Confirm cwd is the client app's root (NOT feedback-tool-mhosaic — see Step 0.5).
120
+ 2. Parse the pasted handoff payload (endpoint, key, origins).
121
+ 3. Detect the framework from `package.json` → route to `references/consumer-install-<framework>.md` (Vite, Next, Nuxt, Astro, Remix, Vue, SvelteKit, or plain/CDN).
122
+ 4. Run `npx @mhosaic/feedback-cli@latest init --api-key=… --endpoint=… --yes`.
123
+ 5. Apply the framework-specific snippet (CLI auto-wraps Vite+React; other frameworks need a manual paste — Claude does this via `Edit`).
124
+ 6. Wire `fb.identify()` per `references/identify-snippets.md` — pick the recipe matching the client's auth provider.
125
+ 7. Run `mhosaic-feedback verify --origin <dev-url> --with-test-report` — all 6 checks must be green before proceeding.
126
+ 8. Start the dev server, drive Chrome to the dev URL, screenshot the FAB, submit a `[INTEGRATE-FEEDBACK SMOKE TEST]` report.
127
+ 9. Drive Chrome to software-factory `/reports`, confirm the report landed.
128
+
129
+ Smoke test details + diagnostic table: **`references/verify-install.md`**.
91
130
 
92
131
  ---
93
132
 
94
133
  ## Operator → consumer handoff payload format
95
134
 
96
- Always produce this Markdown block at the end of operator mode. The consumer pastes it as the very first message to Claude after running `/integrate-feedback` in their own repo:
135
+ Always print this Markdown block at the end of operator phase:
97
136
 
98
137
  ````markdown
99
138
  ## Mhosaic Feedback handoff
@@ -103,7 +142,8 @@ Always produce this Markdown block at the end of operator mode. The consumer pas
103
142
  **Public widget key:** `pk_proj_xxxxxxxxxxxxxxxxxxxxxxxxxx`
104
143
  **Allowed origin(s):** `https://your-app.example.com`, `http://localhost:5173`
105
144
 
106
- To install:
145
+ To install (from the client's app directory):
146
+
107
147
  ```bash
108
148
  npx @mhosaic/feedback-cli@latest init \
109
149
  --api-key pk_proj_xxxxxxxxxxxxxxxxxxxxxxxxxx \
@@ -111,28 +151,33 @@ npx @mhosaic/feedback-cli@latest init \
111
151
  --yes
112
152
  ```
113
153
 
114
- Then verify:
115
- ```bash
116
- npx @mhosaic/feedback-cli@latest verify \
117
- --origin http://localhost:5173 \
118
- --with-test-report
119
- ```
154
+ Or for the full guided flow: `/integrate-feedback` → "Install (consumer)" → paste this block as your first message.
120
155
  ````
121
156
 
122
- The skill's consumer mode parses this block automatically when pasted as input.
157
+ ---
158
+
159
+ ## Principles
160
+
161
+ - **Never commit the API key to git. Ever.** Even `pk_proj_…` "public" keys. Full rules + scenario table at the top of `references/consumer-install.md` under "CRITICAL: API key handling". If the user asks "can I commit this?" the answer is NO; do not rationalize. If `.env.local` is excluded from the host's build context (common with Docker), use the deploy-platform's env-var UI, NOT a tracked `.env`.
162
+ - **Confirm at every fork.** Never assume a default. Use `AskUserQuestion` for choices, plain text for free-form input.
163
+ - **Show, don't tell.** When you can run a command and show output, run it. When you can drive Chrome and screenshot, drive Chrome.
164
+ - **Stop on first red.** If the smoke test fails, don't silently retry. Read `references/verify-install.md`'s diagnostic table, narrow the cause, fix it.
165
+ - **Don't claim "done" without proof.** End the workflow with a screenshot of the smoke-test report landing in admin OR an enumerated checklist of what's still broken.
123
166
 
124
167
  ---
125
168
 
126
169
  ## Skill files
127
170
 
128
171
  ```
129
- skills/integrate-feedback/
130
- ├── SKILL.md # this file (mode router)
172
+ .claude/skills/integrate-feedback/
173
+ ├── SKILL.md # this file (phase router)
131
174
  ├── references/
132
175
  │ ├── operator-provision.md # Chrome MCP → admin SPA → DRF
133
176
  │ ├── consumer-install.md # framework router
134
177
  │ ├── consumer-install-vite.md # Vite + React (CLI auto-wires)
135
178
  │ ├── consumer-install-next.md # Next.js App Router + Pages
179
+ │ ├── consumer-install-nuxt.md # Nuxt 3 (client plugin)
180
+ │ ├── consumer-install-astro.md # Astro (<script> in Layout)
136
181
  │ ├── consumer-install-remix.md # Remix root.tsx
137
182
  │ ├── consumer-install-vue.md # Vue 3 + Vite
138
183
  │ ├── consumer-install-svelte.md # SvelteKit
@@ -0,0 +1,83 @@
1
+ # Consumer install — Astro
2
+
3
+ The CLI's auto-wrap is React-only; for Astro you'll wire the widget into a client-only Astro component (or via the renderer of your choice).
4
+
5
+ ---
6
+
7
+ ## Step 1 — Env vars
8
+
9
+ Astro uses Vite under the hood. Env vars meant for the browser need the `PUBLIC_` prefix:
10
+
11
+ ```diff
12
+ - VITE_FEEDBACK_API_KEY=pk_proj_…
13
+ - VITE_FEEDBACK_ENDPOINT=https://…
14
+ + PUBLIC_FEEDBACK_API_KEY=pk_proj_…
15
+ + PUBLIC_FEEDBACK_ENDPOINT=https://software-factory-3tbbu.ondigitalocean.app
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Step 2 — Add a client-only widget mount
21
+
22
+ Astro doesn't ship JS by default; you have to opt-in with `client:*` directives or via `<script>` blocks. The cleanest path is a `<script>` block in your shared layout:
23
+
24
+ ```astro
25
+ ---
26
+ // src/layouts/Layout.astro
27
+ const apiKey = import.meta.env.PUBLIC_FEEDBACK_API_KEY
28
+ const endpoint = import.meta.env.PUBLIC_FEEDBACK_ENDPOINT
29
+ ---
30
+ <html>
31
+ <head><slot name="head" /></head>
32
+ <body>
33
+ <slot />
34
+
35
+ <script>
36
+ import { createFeedback } from '@mhosaic/feedback/loader'
37
+ import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
38
+ import { withReplay } from '@mhosaic/feedback/replay'
39
+ import { withWebVitals } from '@mhosaic/feedback/webvitals'
40
+
41
+ const apiKey = import.meta.env.PUBLIC_FEEDBACK_API_KEY
42
+ const endpoint = import.meta.env.PUBLIC_FEEDBACK_ENDPOINT
43
+ if (apiKey && endpoint) {
44
+ withErrorTracking(withReplay(withWebVitals(createFeedback({
45
+ apiKey, endpoint, env: import.meta.env.PROD ? 'prod' : 'dev',
46
+ }))))
47
+ }
48
+ </script>
49
+ </body>
50
+ </html>
51
+ ```
52
+
53
+ Astro will bundle the script and run it on the client. The widget mounts to its own Shadow DOM, independent of Astro's island hydration.
54
+
55
+ ---
56
+
57
+ ## Step 3 — Wire identify()
58
+
59
+ How you do this depends on what auth backs your Astro site:
60
+
61
+ - **Astro auth integration (e.g. `@auth/astro`, Lucia)** — call `fb.identify()` from a client-only component that reads the session via the matching SDK.
62
+ - **Server-rendered session passed to client via cookies / `window.ENV` pattern** — same trick as Remix: in `Layout.astro` render a small inline `<script>` that surfaces the user via `window.__USER`, then call `fb.identify(window.__USER)` from the import-script block.
63
+ - **Anonymous-only / mostly-static site** — use the anonymous snippet from `references/identify-snippets.md` so the FAB renders for everyone.
64
+
65
+ If the site is mostly static (marketing, docs), anonymous is the typical pick — visitors aren't logged in.
66
+
67
+ ---
68
+
69
+ ## Gotchas
70
+
71
+ - **No SSR window:** Astro's component code runs on the server. Only code inside `<script>` blocks (or `client:*`-hydrated components) runs in the browser. Always wrap the widget init inside `<script>`.
72
+ - **View Transitions API:** if you use Astro's `<ViewTransitions />`, the widget mount survives navigations (it's outside Astro's routed area, in `<body>`). Good — no re-mount churn.
73
+ - **Build output type:** the widget needs `output: 'server'` or `output: 'hybrid'` only if you want the SSR-side env injection; for static (`output: 'static'`), env vars are inlined at build time and the widget still works.
74
+
75
+ ---
76
+
77
+ ## Verifying
78
+
79
+ ```bash
80
+ npx @mhosaic/feedback-cli@latest verify --origin http://localhost:4321 --with-test-report
81
+ ```
82
+
83
+ Astro dev defaults to port 4321.
@@ -27,7 +27,7 @@ Create a new file `app/components/FeedbackMount.tsx`:
27
27
  'use client'
28
28
 
29
29
  import { useEffect } from 'react'
30
- import { createFeedback } from '@mhosaic/feedback'
30
+ import { createFeedback } from '@mhosaic/feedback/loader'
31
31
  import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
32
32
  import { withReplay } from '@mhosaic/feedback/replay'
33
33
  import { withWebVitals } from '@mhosaic/feedback/webvitals'
@@ -76,7 +76,7 @@ Edit `pages/_app.tsx`:
76
76
  ```typescript
77
77
  import { useEffect } from 'react'
78
78
  import type { AppProps } from 'next/app'
79
- import { createFeedback } from '@mhosaic/feedback'
79
+ import { createFeedback } from '@mhosaic/feedback/loader'
80
80
  import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
81
81
  import { withReplay } from '@mhosaic/feedback/replay'
82
82
  import { withWebVitals } from '@mhosaic/feedback/webvitals'
@@ -0,0 +1,108 @@
1
+ # Consumer install — Nuxt 3
2
+
3
+ The CLI's auto-wrap is React-only; for Nuxt you paste the snippet into a client-only plugin.
4
+
5
+ ---
6
+
7
+ ## Step 1 — Env vars
8
+
9
+ Nuxt 3 exposes env vars via `runtimeConfig`. Open `.env` (or `.env.local` — Nuxt reads both) and rename:
10
+
11
+ ```diff
12
+ - VITE_FEEDBACK_API_KEY=pk_proj_…
13
+ - VITE_FEEDBACK_ENDPOINT=https://…
14
+ + NUXT_PUBLIC_FEEDBACK_API_KEY=pk_proj_…
15
+ + NUXT_PUBLIC_FEEDBACK_ENDPOINT=https://software-factory-3tbbu.ondigitalocean.app
16
+ ```
17
+
18
+ Add the matching `runtimeConfig.public` entries to `nuxt.config.ts`:
19
+
20
+ ```typescript
21
+ export default defineNuxtConfig({
22
+ runtimeConfig: {
23
+ public: {
24
+ feedbackApiKey: '',
25
+ feedbackEndpoint: '',
26
+ },
27
+ },
28
+ })
29
+ ```
30
+
31
+ Nuxt auto-binds `NUXT_PUBLIC_*` env vars to the matching `runtimeConfig.public.*` keys at runtime.
32
+
33
+ ---
34
+
35
+ ## Step 2 — Add a client-only plugin
36
+
37
+ Create `plugins/feedback.client.ts` (the `.client.ts` suffix tells Nuxt to bundle this only for the browser, not SSR):
38
+
39
+ ```typescript
40
+ import { createFeedback } from '@mhosaic/feedback/loader'
41
+ import { withErrorTracking } from '@mhosaic/feedback/error-tracking'
42
+ import { withReplay } from '@mhosaic/feedback/replay'
43
+ import { withWebVitals } from '@mhosaic/feedback/webvitals'
44
+
45
+ export default defineNuxtPlugin(() => {
46
+ const config = useRuntimeConfig()
47
+ const fb = withErrorTracking(
48
+ withReplay(
49
+ withWebVitals(createFeedback({
50
+ apiKey: config.public.feedbackApiKey as string,
51
+ endpoint: config.public.feedbackEndpoint as string,
52
+ env: process.dev ? 'dev' : 'prod',
53
+ }))
54
+ )
55
+ )
56
+ return { provide: { feedback: fb } }
57
+ })
58
+ ```
59
+
60
+ Now `useNuxtApp().$feedback` is available across the app.
61
+
62
+ ---
63
+
64
+ ## Step 3 — Wire identify()
65
+
66
+ Nuxt's auth is whatever you bolt on (sidebase/nuxt-auth, custom, Supabase, etc.). Use the matching snippet from `references/identify-snippets.md`, called from a `watchEffect` inside `app.vue` or a top-level `<script setup>` that re-runs on auth state changes:
67
+
68
+ ```vue
69
+ <!-- app.vue -->
70
+ <script setup lang="ts">
71
+ import { useAuthStore } from '~/stores/auth' // adapt to your auth source
72
+
73
+ const auth = useAuthStore()
74
+ const { $feedback } = useNuxtApp()
75
+
76
+ watchEffect(() => {
77
+ if (auth.user) {
78
+ $feedback.identify({ id: auth.user.id, email: auth.user.email, name: auth.user.name })
79
+ } else {
80
+ $feedback.identify({ id: '', email: '', name: '' })
81
+ }
82
+ })
83
+ </script>
84
+
85
+ <template>
86
+ <NuxtLayout>
87
+ <NuxtPage />
88
+ </NuxtLayout>
89
+ </template>
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Gotchas
95
+
96
+ - **SSR + window:** Nuxt SSRs everything by default. The `.client.ts` suffix on the plugin ensures the widget code never runs on the Nitro server (where `window` doesn't exist).
97
+ - **`useRuntimeConfig()` returns empty strings on first render** if the env vars weren't set — Nuxt warns at startup. Verify your `.env` has the right `NUXT_PUBLIC_*` keys.
98
+ - **Static site mode (`nuxt generate`):** the widget hydrates at runtime, same as Next.js. Generated pages render without the FAB until JS hydrates.
99
+
100
+ ---
101
+
102
+ ## Verifying
103
+
104
+ ```bash
105
+ npx @mhosaic/feedback-cli@latest verify --origin http://localhost:3000 --with-test-report
106
+ ```
107
+
108
+ Nuxt dev defaults to port 3000.
@@ -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.), use the CDN-hosted IIFE bundle. No npm install needed.
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
- ## Step 1 — Find the latest version + SRI hash
7
+ ---
8
8
 
9
- The bundle is published to jsDelivr from the npm release. Get the current version + integrity hash from the top-level `README.md` of the feedback-tool-mhosaic repo (or from the operator). The format is:
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@<VERSION>/dist/embed.min.js"
14
- integrity="sha384-<HASH>"
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
- Pin the **exact** version. Don't use `@latest` that breaks SRI and gives you no rollback story. The operator can update the version on a deploy cycle.
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
- ## Step 2 Insert into your template
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
- - `script-src https://cdn.jsdelivr.net 'sha384-<HASH>'`
33
- - `connect-src https://software-factory-3tbbu.ondigitalocean.app`
34
- - `worker-src 'blob:'` (only if you'll later upgrade to a version that ships rrweb replay)
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 3 — Wire identify() via the queue API
37
+ ## Step 2 — Wire identify() via the queue API
39
38
 
40
- The CDN bundle exposes a global function `window.Feedback` that queues calls until the bundle finishes loading. Drop this **before** the `<script src=…>` tag so the queue exists when the bundle initializes:
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 = window.Feedback || function () {
45
- (window.Feedback.q = window.Feedback.q || []).push(arguments)
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
- Feedback('identify', { id: 'anon-' + (localStorage.getItem('anonId') || (localStorage.setItem('anonId', crypto.randomUUID()), localStorage.getItem('anonId'))), name: 'Anonymous' })
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 on the CDN bundle.** The CDN IIFE is the bare widget. `withErrorTracking` / `withReplay` / `withWebVitals` are available only via npm import. If the user wants those, they must switch to the bundled (npm) install — drop the `<script>` tag and follow the framework-specific reference instead.
77
- - **SRI hash must match the bytes** at the pinned version. If you bump the version, regenerate the hash:
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 yet.** `withErrorTracking` / `withReplay` / `withWebVitals` are middleware wrappers that ship via the npm path. The CDN install gives you the bare manual-submit FAB. 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 FAB renders (visual check; bottom-right corner)
92
- 4. Submitting a test report lands in `/reports` (see `references/verify-install.md`)
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`)
@@ -23,7 +23,7 @@ In `app/root.tsx`, add a loader that surfaces the values to the client window:
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
28
  import { withReplay } from '@mhosaic/feedback/replay'
29
29
  import { withWebVitals } from '@mhosaic/feedback/webvitals'
@@ -25,7 +25,7 @@ Edit `src/routes/+layout.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
30
  import { withReplay } from '@mhosaic/feedback/replay'
31
31
  import { withWebVitals } from '@mhosaic/feedback/webvitals'