@mhosaic/feedback-cli 0.19.0 → 0.21.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.
Files changed (31) hide show
  1. package/dist/bin.js +6 -1
  2. package/dist/bin.js.map +1 -1
  3. package/dist/build-RCLWV2WL.js +565 -0
  4. package/dist/build-RCLWV2WL.js.map +1 -0
  5. package/dist/check-FOJPEE4Q.js +342 -0
  6. package/dist/check-FOJPEE4Q.js.map +1 -0
  7. package/dist/chunk-AZQSQJNQ.js +83 -0
  8. package/dist/chunk-AZQSQJNQ.js.map +1 -0
  9. package/dist/chunk-IHJPCMYF.js +126 -0
  10. package/dist/chunk-IHJPCMYF.js.map +1 -0
  11. package/dist/chunk-SSLQOK2Z.js +456 -0
  12. package/dist/chunk-SSLQOK2Z.js.map +1 -0
  13. package/dist/config-JE3QRZVH.js +8 -0
  14. package/dist/config-JE3QRZVH.js.map +1 -0
  15. package/dist/generate-VFRQNPOI.js +14 -0
  16. package/dist/generate-VFRQNPOI.js.map +1 -0
  17. package/dist/{install-skill-QJ4ZDVVR.js → install-skill-PO5YSXWY.js} +4 -2
  18. package/dist/{install-skill-QJ4ZDVVR.js.map → install-skill-PO5YSXWY.js.map} +1 -1
  19. package/dist/qa-YR4GCV6T.js +43 -0
  20. package/dist/qa-YR4GCV6T.js.map +1 -0
  21. package/dist/sitemap-react-QTEAUKN5.js +330 -0
  22. package/dist/sitemap-react-QTEAUKN5.js.map +1 -0
  23. package/dist/sitemap-vue-EJDQTCYM.js +243 -0
  24. package/dist/sitemap-vue-EJDQTCYM.js.map +1 -0
  25. package/package.json +4 -3
  26. package/skills/integrate-feedback/SKILL.md +7 -0
  27. package/skills/integrate-qa-meter/SKILL.md +201 -0
  28. package/skills/integrate-qa-meter/references/qa-meter-config.md +168 -0
  29. package/skills/integrate-qa-meter/references/qa-meter-troubleshooting.md +37 -0
  30. package/skills/integrate-qa-meter/references/qa-meter.config.example.json +20 -0
  31. package/skills/integrate-qa-meter/references/qa-meter.config.example.react.json +19 -0
@@ -0,0 +1,168 @@
1
+ # QA Meter — config reference, framework matrix, surfaces, CI
2
+
3
+ Everything needed to author `qa-meter.config.json`, enable the FAB, and wire CI. Self-contained (this file ships in the CLI tarball; it does not assume the repo `docs/` are present).
4
+
5
+ ---
6
+
7
+ ## `qa-meter.config.json`
8
+
9
+ Discovered in **cwd only** (no walk-up) by every `qa` subcommand. All path fields resolve **relative to the config file's directory**, except `testsGlob` / `fixturesFile` / `pageObjectsDir`, which resolve **relative to `e2eDir`**. Put the file at the common parent of your e2e dir and your app dir (in a monorepo, that's usually one level up from both) and use relative paths into each.
10
+
11
+ | Field | Required | Default | Meaning |
12
+ | --- | --- | --- | --- |
13
+ | `e2eDir` | ✅ | — | Playwright e2e root. |
14
+ | `suitesDir` | ✅ | — | Where suites live: hand-authored at the root, generated ones under `<suitesDir>/generated/`. |
15
+ | `outFile` | ✅ | — | Where `qa-status.json` is written. Point it inside the app's served static dir. |
16
+ | `testsGlob` | | `tests/**/*.spec.ts` | Spec glob, relative to `e2eDir`. |
17
+ | `fixturesFile` | | `helpers/fixtures.ts` | Playwright fixtures file (fixture name → page-object class), relative to `e2eDir`. |
18
+ | `pageObjectsDir` | | `pages` | Page-objects dir (classes with `goto()`), relative to `e2eDir`. |
19
+ | `annotationPrefix` | | `@mhosaic` | Honored annotation prefix in spec headers (`@mhosaic:pages`, `@mhosaic:scenario`). |
20
+ | `environment` | | `staging` | Stamped into the artifact (shown in the modal header). |
21
+ | `appVersion` | | `null` | App version stamp; or pass `--version <v>` on the CLI. |
22
+ | `resultsFile` | | `null` | Playwright JSON report. **Absent ⇒ every scenario is `never_run` (gray).** |
23
+ | `devValidationsFile` | | `null` | Append-only dev-validation ledger (human "I checked this" lane). |
24
+ | `sitemap` | | `null` | Sitemap block (below). Omit for a flat page list (no tree). |
25
+
26
+ ### `sitemap` block
27
+
28
+ | Field | Required | Meaning |
29
+ | --- | --- | --- |
30
+ | `file` | ✅ (if block present) | Where the sitemap JSON is written (by `qa sitemap`) and read (by `qa build`). |
31
+ | `framework` | | Adapter name, e.g. `"vue-router"`. **Set ⇒ `qa refresh` runs `qa sitemap`. Omit ⇒ you hand-provide `file`.** |
32
+ | `entry` | | **react-router:** the file holding the route tree (`frontend/src/App.tsx` or `frontend/src/router.tsx`). Preferred over `routerDir` for react. |
33
+ | `routerStyle` | | **react-router:** force the reader — `"jsx"` or `"data"`. Omit to auto-detect. |
34
+ | `routerDir` | | Dir holding the route declarations + entry (an `index.*` that exports `routes`). |
35
+ | `titlesFrom` | | Locale JSON for human page titles. Looked up by route `name` under `pages.<name>` (or a root-level `<name>`). |
36
+ | `alias` | | Import-alias map, e.g. `{ "@": "frontend/src" }`. Values resolve relative to the config dir; the adapter follows only in-tree (within `routerDir`) imports and stubs the rest. |
37
+
38
+ ### Worked template (vue-router + Playwright)
39
+
40
+ > Shipped next to this file as a copy-pasteable **`qa-meter.config.example.json`** — `cp` it to your project root as `qa-meter.config.json` and edit the paths. (Strict JSON: the loader uses `JSON.parse`, so no comments.)
41
+
42
+ ```json
43
+ {
44
+ "e2eDir": "e2e",
45
+ "suitesDir": "qa-meter/suites",
46
+ "outFile": "frontend/public/qa-status.json",
47
+ "testsGlob": "tests/**/*.spec.ts",
48
+ "fixturesFile": "helpers/fixtures.ts",
49
+ "pageObjectsDir": "pages",
50
+ "environment": "staging",
51
+ "appVersion": null,
52
+ "resultsFile": null,
53
+ "sitemap": {
54
+ "file": "qa-meter/sitemap.json",
55
+ "framework": "vue-router",
56
+ "routerDir": "frontend/src/router",
57
+ "titlesFrom": "frontend/src/locales/fr.json",
58
+ "alias": { "@": "frontend/src" }
59
+ }
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Framework matrix
66
+
67
+ | Capability | Supported | Escape hatch when not |
68
+ | --- | --- | --- |
69
+ | **Spec analysis** (`qa generate`) | **Playwright** (static parse of specs + fixtures + page-objects; no execution) | No Playwright → skip `generate`; hand-author suites in `suitesDir` (see `docs/qa-meter/SUITE-FORMAT.md`), or run sitemap-only. |
70
+ | **Sitemap** (`qa sitemap`) | **vue-router** (`framework: "vue-router"`, `routerDir`) and **react-router** (`framework: "react-router"`, `entry`) | Other router → omit `framework` and hand-provide `sitemap.file` (a `format: 1` SitemapPayload you author). Or omit the whole `sitemap` block → flat page list from the suites' page patterns. |
71
+ | **Test results** (`qa build`) | **Playwright JSON report** via `resultsFile` | No results → all `never_run`/gray (the honest default). |
72
+
73
+ The adapter reads `routerDir`'s entry (`index.ts/.mjs/.js` exporting `routes`, else the first file that does), transpiles + evaluates it in a sandbox, follows in-tree imports (resolved through `alias`), and stubs out-of-tree ones (stores/components/enums). It handles lazy `() => import()`, nested `children`, spread route arrays, `redirect`, `name`, and dynamic `:segments`. If a route module throws at eval because it *uses* an out-of-tree value at module top-level, move that usage out of the eval path or hand-provide the sitemap.
74
+
75
+ **react-router (v7 declarative).** Set `framework: "react-router"` and `entry` to the file
76
+ holding the route tree — either JSX `<Routes><Route/>` (e.g. `src/App.tsx`) or
77
+ `createBrowserRouter([…])` (e.g. `src/router.tsx`). This adapter reads routes **statically**
78
+ from the TypeScript syntax tree (no app boot, no sandbox eval). The style is auto-detected;
79
+ force it with `routerStyle: "jsx" | "data"`. Limits, all WARNED (never silently dropped):
80
+ non-literal `path={expr}` and dynamically-composed object routes (spreads / imported arrays /
81
+ `.map()`). Catch-all `*` and root-element `<Navigate>` redirects are dropped; a `<Navigate>`
82
+ nested inside a guard/wrapper element honestly surfaces as a page.
83
+
84
+ ---
85
+
86
+ ## Enabling the FAB (surfaces)
87
+
88
+ `source` is the only required field. For a host install it's the URL your app serves the artifact at (a string); it also accepts an inline artifact object or a `() => Promise<artifact>` loader for advanced cases.
89
+
90
+ ```ts
91
+ // npm
92
+ createFeedback({
93
+ apiKey: 'pk_proj_…',
94
+ endpoint: 'https://…',
95
+ qaMeter: {
96
+ source: '/qa-status.json', // required
97
+ locale: 'fr', // 'fr' | 'en' — defaults to the feedback widget's resolved locale
98
+ size: 'sm', // 'sm' | 'md' (default 'sm' — the half-size second FAB)
99
+ // position: { right: 36, bottom: 84 }, // default auto-stacks above the feedback FAB
100
+ // getCurrentPage: () => location.pathname, // override route resolution
101
+ },
102
+ })
103
+ ```
104
+
105
+ ```tsx
106
+ // React — FeedbackProvider forwards the prop unchanged
107
+ <FeedbackProvider apiKey="…" endpoint="…" qaMeter={{ source: '/qa-status.json' }}>
108
+ {children}
109
+ </FeedbackProvider>
110
+ ```
111
+
112
+ ```html
113
+ <!-- CDN / loader embed -->
114
+ <script
115
+ src="https://cdn.jsdelivr.net/npm/@mhosaic/feedback@<version>/dist/embed.min.js"
116
+ data-key="pk_proj_…"
117
+ data-endpoint="https://…"
118
+ data-qa-source="/qa-status.json"
119
+ data-qa-locale="fr"
120
+ defer
121
+ ></script>
122
+ ```
123
+
124
+ **Host-gate it:** only set `qaMeter.source` / `data-qa-source` where you want the meter (staging, internal dev). Omit it in production to ship feedback-only. A common pattern is to drive it from an env var that's only set in non-prod builds.
125
+
126
+ ---
127
+
128
+ ## Consuming an unreleased build
129
+
130
+ The `qaMeter` option exists only on `@mhosaic/feedback` builds that ship the second-FAB integration. To test against an unreleased build (e.g. a feature branch), pack and install the local tarball:
131
+
132
+ ```bash
133
+ # in the feedback-tool-mhosaic monorepo
134
+ pnpm --filter @mhosaic/feedback build
135
+ ( cd packages/core && pnpm pack ) # → packages/core/mhosaic-feedback-<ver>.tgz
136
+
137
+ # in the host app
138
+ npm i /abs/path/to/packages/core/mhosaic-feedback-<ver>.tgz
139
+ # or yarn add file:/abs/path/...
140
+ ```
141
+
142
+ Once the feature is released, switch back to a semver range.
143
+
144
+ ---
145
+
146
+ ## CI wiring
147
+
148
+ Rebuild the artifact whenever specs or routes change. The high-value job runs **after** e2e and feeds real results so the meter shows true color:
149
+
150
+ ```yaml
151
+ # after the e2e job has produced a Playwright JSON report
152
+ - name: Build QA Meter artifact
153
+ run: |
154
+ npx @mhosaic/feedback-cli@latest qa refresh --version "${{ github.sha }}"
155
+ # config.resultsFile → the playwright JSON report (e.g. e2e/results.json)
156
+ - name: Publish with static assets
157
+ run: cp frontend/public/qa-status.json "$BUILD_OUTPUT/" # ships like any public/ file
158
+ ```
159
+
160
+ Generate the Playwright JSON with `playwright test --reporter=json --output=… > e2e/results.json` (or a `json` reporter in `playwright.config`), and set `resultsFile` to that path.
161
+
162
+ **Tripwire (optional):** fail CI when the committed inputs drift from what the artifact represents:
163
+
164
+ ```bash
165
+ npx @mhosaic/feedback-cli@latest qa check --strict
166
+ ```
167
+
168
+ All `qa` subcommands accept `--version <app_version>`; `check` accepts `--strict`.
@@ -0,0 +1,37 @@
1
+ # QA Meter — troubleshooting
2
+
3
+ Diagnostics for the `/integrate-qa-meter` flow. Stop on the first red, narrow the cause, fix it — don't silently retry.
4
+
5
+ | Symptom | Cause | Fix |
6
+ | --- | --- | --- |
7
+ | **Meter is all gray, "0% scenarios passing"** | No test results fed — `resultsFile: null`, so every scenario is `never_run`. This is the honesty model, not a bug. | Run the Playwright suite, write a JSON report, set `resultsFile` to it, re-run `qa build` (or `qa refresh`). Scenarios with a matching passing result turn green. |
8
+ | **QA pastille never appears** (no `[data-mqa-host]` on `<body>`) | `qaMeter.source` not set on the surface, OR `createFeedback` never runs, OR the installed `@mhosaic/feedback` predates the second-FAB integration. | Confirm the source is wired (Step 4). Confirm `createFeedback`/`<FeedbackProvider>`/embed actually executes. Check the installed version supports `qaMeter` (`references/qa-meter-config.md` → "Consuming an unreleased build"). |
9
+ | **`[data-mqa-host]` exists but the pastille is gray "unknown page" on every route** | The current `location.pathname` doesn't match any sitemap/page pattern — usually the sitemap patterns don't reflect the app's real routes. | Inspect `qa-status.json` → `sitemap[].path_pattern`; compare to real URLs. Re-check `routerDir`/`alias` produced the right patterns. For custom routing, pass `qaMeter.getCurrentPage: () => <pattern>`. |
10
+ | **Artifact 404s at runtime** | `outFile` isn't under the served static dir, or `source` points at the wrong URL. | Move `outFile` into the app's static dir (e.g. `public/`); set `source` to the served path. Verify: `curl -s <origin>/qa-status.json -o /dev/null -w "%{http_code}\n"` → `200`. |
11
+ | **`qa sitemap` errors / "No router entry found"** | `routerDir` wrong, `alias` missing/incorrect, the entry doesn't export `routes`, or a route module throws at eval (uses an out-of-tree value at module top-level). | Point `routerDir` at the dir whose `index.*` exports `routes`. Set `alias` (e.g. `{ "@": "<src>" }`). For non-vue-router or a stubborn module, hand-provide `sitemap.file` and omit `framework`. |
12
+ | **Lots of `unmappable` / `zero-case` warnings from `qa generate`** | Specs whose target pages can't be inferred statically (no destructured page-object fixture, no literal `goto()`), or factory/loop/dynamic-title tests that yield no static case. | Not fatal — the artifact still builds. To represent them, add `@mhosaic:pages /route, /other` or `@mhosaic:scenario <id>` to the spec header, or give tests string-literal titles. See `docs/qa-meter/SUITE-FORMAT.md`. Surface the count to the user. |
13
+ | **Console: "QA Meter artifact has an unsupported `format` — refusing to mount"** | The artifact's `format` doesn't match what the installed widget expects (CLI/widget version skew). | Regenerate with a CLI version matching the widget build; bump both together. |
14
+ | **Meter shows stale data** | The artifact wasn't rebuilt after specs/routes changed. | Re-run `qa refresh`; wire it into CI after the e2e job (`references/qa-meter-config.md` → CI wiring). |
15
+ | **Pastille overlaps the feedback FAB or sits in the wrong corner** | Custom feedback-FAB position, or a host element competing for the bottom-right. | Set `qaMeter.position: { right, bottom }` to nudge it; the default stacks it above the feedback FAB. |
16
+ | **`qa generate` finds 0 cases across the whole suite** | `fixturesFile` / `pageObjectsDir` / `testsGlob` point at the wrong place, so nothing is parsed. | Re-check those three against `e2eDir`. Confirm the fixtures file is the one specs import `test` from and that page objects expose `goto()`. |
17
+
18
+ If a step still fails after narrowing, capture: the full CLI output, the relevant slice of `qa-status.json` (or `sitemap.json`), and the config — then escalate or open an issue against `feedback-tool-mhosaic`.
19
+
20
+ ---
21
+
22
+ ## Quick browser checks (Step 5)
23
+
24
+ ```js
25
+ // pastille mounted + half size?
26
+ const host = document.querySelector('[data-mqa-host]')
27
+ host && host.getAttribute('data-mqa-size') // → "sm"
28
+
29
+ // artifact reachable?
30
+ fetch('/qa-status.json').then(r => r.status) // → 200
31
+
32
+ // open the sitemap modal programmatically (clicks the pastille)
33
+ document.querySelector('[data-mqa-host]')
34
+ ?.shadowRoot?.querySelector('[data-testid=qa-pastille]')?.click()
35
+ ```
36
+
37
+ The pastille resolves the current route on SPA navigations (it patches `pushState`/`replaceState` and listens for `popstate`), so its color/aria updates as you navigate without a reload.
@@ -0,0 +1,20 @@
1
+ {
2
+ "e2eDir": "e2e",
3
+ "suitesDir": "qa-meter/suites",
4
+ "outFile": "frontend/public/qa-status.json",
5
+ "testsGlob": "tests/**/*.spec.ts",
6
+ "fixturesFile": "helpers/fixtures.ts",
7
+ "pageObjectsDir": "pages",
8
+ "annotationPrefix": "@mhosaic",
9
+ "environment": "staging",
10
+ "appVersion": null,
11
+ "resultsFile": null,
12
+ "devValidationsFile": null,
13
+ "sitemap": {
14
+ "file": "qa-meter/sitemap.json",
15
+ "framework": "vue-router",
16
+ "routerDir": "frontend/src/router",
17
+ "titlesFrom": "frontend/src/locales/fr.json",
18
+ "alias": { "@": "frontend/src" }
19
+ }
20
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "e2eDir": "e2e",
3
+ "suitesDir": "qa-meter/suites",
4
+ "outFile": "frontend/public/qa-status.json",
5
+ "testsGlob": "tests/**/*.spec.ts",
6
+ "fixturesFile": "helpers/fixtures.ts",
7
+ "pageObjectsDir": "pages",
8
+ "annotationPrefix": "@mhosaic",
9
+ "environment": "staging",
10
+ "appVersion": null,
11
+ "resultsFile": null,
12
+ "devValidationsFile": null,
13
+ "sitemap": {
14
+ "file": "qa-meter/sitemap.json",
15
+ "framework": "react-router",
16
+ "entry": "frontend/src/App.tsx",
17
+ "routerStyle": "jsx"
18
+ }
19
+ }