@artymclabin/qa-review 0.3.7
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/CHANGELOG.md +174 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/client/QAReviewOverlay.d.ts +59 -0
- package/dist/client/QAReviewOverlay.js +858 -0
- package/dist/client/device.d.ts +28 -0
- package/dist/client/device.js +57 -0
- package/dist/client/fingerprint.d.ts +19 -0
- package/dist/client/fingerprint.js +40 -0
- package/dist/client/highlight.d.ts +17 -0
- package/dist/client/highlight.js +77 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.js +11 -0
- package/dist/client/journey.d.ts +89 -0
- package/dist/client/journey.js +134 -0
- package/dist/client/preview.d.ts +30 -0
- package/dist/client/preview.js +116 -0
- package/dist/client/revisit.d.ts +28 -0
- package/dist/client/revisit.js +27 -0
- package/dist/client/store.d.ts +100 -0
- package/dist/client/store.js +234 -0
- package/dist/client/styles.d.ts +3 -0
- package/dist/client/styles.js +211 -0
- package/dist/client/types.d.ts +87 -0
- package/dist/client/types.js +7 -0
- package/dist/server/handlers.d.ts +31 -0
- package/dist/server/handlers.js +186 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/server/storage.d.ts +78 -0
- package/dist/server/storage.js +245 -0
- package/dist/shared/codename.d.ts +16 -0
- package/dist/shared/codename.js +55 -0
- package/package.json +61 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.7 - 2026-07-30
|
|
4
|
+
|
|
5
|
+
- MOBILE PREVIEW SCROLLS TO THE ITEM: the phone-frame iframe loaded each page
|
|
6
|
+
at its top, so opening the preview showed the header rather than the section
|
|
7
|
+
under review. scrollPreviewToSelector() now reaches into the same-origin
|
|
8
|
+
frame and centres the current item's element. It RETRIES rather than
|
|
9
|
+
scrolling once, because the iframe fires load before hydration and before
|
|
10
|
+
images settle and an early scroll gets undone by the layout shift that
|
|
11
|
+
follows; it keeps scrolling for a couple of rounds after a successful hit for
|
|
12
|
+
the same reason, and is bounded at 12 attempts so a genuinely absent element
|
|
13
|
+
(desktop-only items) cannot loop forever. Returns a cleanup that cancels
|
|
14
|
+
pending retries.
|
|
15
|
+
- Wired both on iframe load (opening the preview) and on a selector-keyed
|
|
16
|
+
effect (advancing to the next item while the preview stays open, where the
|
|
17
|
+
iframe is deliberately not reloaded and load never fires again).
|
|
18
|
+
|
|
19
|
+
## 0.3.6 - 2026-07-21
|
|
20
|
+
|
|
21
|
+
- MOBILE PREVIEW LIVE VARIANT UPDATE: picking a variation while the phone-frame
|
|
22
|
+
preview is OPEN now applies the new variant inside the iframe without a
|
|
23
|
+
close+reopen. The parent posts a same-origin variant message to the iframe;
|
|
24
|
+
the embedded (dormant) overlay runs the item's OWN variation callback there,
|
|
25
|
+
so the DOM mutation executes in the iframe document. The frame also syncs the
|
|
26
|
+
current variant on load. New exports: postVariantToPreview, parseVariantMessage.
|
|
27
|
+
- MINIMIZE BUBBLE DOCKS LEFT: the floating bubble now defaults to the LEFT side
|
|
28
|
+
of the screen when minimized (still fully draggable).
|
|
29
|
+
|
|
30
|
+
## 0.3.5 - 2026-07-21
|
|
31
|
+
|
|
32
|
+
- JOURNEY PRELOAD: within the final 2 items of a round the overlay prefetches
|
|
33
|
+
the other pages' pending counts in the background and warms the likely next
|
|
34
|
+
page with a <link rel="prefetch"> hint. Round exhaustion then navigates
|
|
35
|
+
INSTANTLY on the cached result (30s freshness window; stale/missing falls
|
|
36
|
+
back to the on-demand fetch + loading card).
|
|
37
|
+
- MOBILE PREVIEW: a panel button renders the current page in a phone-sized
|
|
38
|
+
(390x844) framed same-origin iframe on a dimmed backdrop - Approve Mobile
|
|
39
|
+
without devtools. The QA card stays usable on top; a close control (or the
|
|
40
|
+
toggle) returns to normal. The iframe URL keeps the QA params plus a
|
|
41
|
+
qaMobilePreview marker that keeps the embedded overlay dormant.
|
|
42
|
+
- ROUND-SCOPED COUNTERS: the finish panel now leads with THIS ROUND's
|
|
43
|
+
approved/rejected (verdicts recorded in this run); ledger totals moved to a
|
|
44
|
+
smaller line labeled "all-time on this page". The footer decided-counter is
|
|
45
|
+
session-based and the approved total is labeled "approved all-time".
|
|
46
|
+
|
|
47
|
+
## 0.3.4 - 2026-07-21
|
|
48
|
+
|
|
49
|
+
- TOOLTIP DE-NESTING: exactly one tooltip per hover point. Container elements
|
|
50
|
+
no longer carry tooltips that overlap a child control's (Copy-ref row ->
|
|
51
|
+
codename span; card header -> counter; bubble -> single carrier), plus a
|
|
52
|
+
CSS :has() guard suppresses any ancestor tooltip while a descendant tooltip
|
|
53
|
+
target is hovered. Rendered-DOM invariant test: no [data-qatip] element may
|
|
54
|
+
have a [data-qatip] ancestor.
|
|
55
|
+
- COPY-REF FORMAT: the copied reference is now brace-wrapped:
|
|
56
|
+
`{ qa-ref: <codename> | <target> # <itemId> | <title> }`.
|
|
57
|
+
- Robustness: the auto-bubble listener no-ops when matchMedia is unavailable.
|
|
58
|
+
|
|
59
|
+
## 0.3.3 - 2026-07-21
|
|
60
|
+
|
|
61
|
+
- RE-QUEUE CONTEXT: invalidation (state POST verdict:null) accepts an optional
|
|
62
|
+
`revisitReason`. With a reason the row is KEPT - verdict moves to
|
|
63
|
+
prev_verdict, note + fingerprint stay, reason is stored - and the card for
|
|
64
|
+
the re-queued item prominently shows "Back for review: <reason>" plus
|
|
65
|
+
"Your last verdict: <verdict> - '<note>'", combined with the fingerprint
|
|
66
|
+
badges (NOT ALTERED / changed). No reason + changed fingerprint shows a
|
|
67
|
+
generic "Content changed since your last review." Recording a new verdict
|
|
68
|
+
consumes the context; verdict:null WITHOUT a reason keeps the old
|
|
69
|
+
hard-delete (undo). State GET returns revisitReason + prevVerdict for
|
|
70
|
+
audits. Migration id 3: revisit_reason + prev_verdict columns.
|
|
71
|
+
|
|
72
|
+
## 0.3.2 - 2026-07-21
|
|
73
|
+
|
|
74
|
+
- CRITICAL journey fix: the walkthrough ping-ponged forever between a page
|
|
75
|
+
with fresh REJECTS and the next page (rejected items counted as pending for
|
|
76
|
+
navigation, so wraparound kept returning to them). Navigation-pending is now
|
|
77
|
+
UNVERDICTED only (no verdict and no device approvals) - approve, reject,
|
|
78
|
+
and partial device states all count as handled for the current run.
|
|
79
|
+
Rejected items still re-enter FUTURE rounds (round/ledger semantics
|
|
80
|
+
unchanged); the journey-complete panel shows when every page has zero
|
|
81
|
+
unverdicted items. New `countUnverdicted` export.
|
|
82
|
+
|
|
83
|
+
## 0.3.1 - 2026-07-21
|
|
84
|
+
|
|
85
|
+
- JOURNEY LOADING INDICATOR: finishing a page in a journey now shows an
|
|
86
|
+
unmistakable spinner card ("Loading <next page>…" / "Checking remaining
|
|
87
|
+
pages…") from the moment the round exhausts until the next page unloads -
|
|
88
|
+
never a blank screen.
|
|
89
|
+
- DEVICE APPROVE TOGGLE: Approve PC / Approve Mobile (and single Approve)
|
|
90
|
+
buttons toggle - clicking an approved device UNSETS that approval and the
|
|
91
|
+
item returns to pending when it loses full approval. Unsets persist in real
|
|
92
|
+
time (fully-approved rows are cleared and re-written with the remaining
|
|
93
|
+
device approvals + retained note/variant/fingerprint, in queue order).
|
|
94
|
+
- AUTO-BUBBLE: switching the viewport to a mobile-ish width (<=767px, e.g.
|
|
95
|
+
devtools responsive emulation) auto-minimizes the panel to the bubble;
|
|
96
|
+
switching back auto-restores. Manual minimize/restore wins until the next
|
|
97
|
+
switch.
|
|
98
|
+
- SUB-HIGHLIGHT RENDER FIX: highlight marks live in page content, outside the
|
|
99
|
+
.qar-theme var scope - the 0.3.0 rule depended on un-fallbacked vars and
|
|
100
|
+
computed to NO background (invisible highlights). The rule now carries hard
|
|
101
|
+
var() fallbacks and the overlay mirrors its theme vars onto <html> so marks
|
|
102
|
+
follow the consumer theme.
|
|
103
|
+
- ELEMENT-PICK MULTI-SELECT: in note pick-element mode, LEFT click keeps the
|
|
104
|
+
pick-and-exit behavior; RIGHT click picks WITHOUT exiting (native context
|
|
105
|
+
menu suppressed) so several elements can be referenced in a row.
|
|
106
|
+
- INSTANT TOOLTIPS: all overlay tooltips are zero-delay CSS tooltips
|
|
107
|
+
([data-qatip]) instead of the ~500ms native title delay.
|
|
108
|
+
|
|
109
|
+
## 0.3.0 - 2026-07-21
|
|
110
|
+
|
|
111
|
+
- NOT-ALTERED POKA-YOKE: every verdict stores a normalized content fingerprint
|
|
112
|
+
(anchored items hash the element innerText; task items their question). A
|
|
113
|
+
re-shown REJECTED item whose content still fingerprints identical gets a
|
|
114
|
+
prominent SYSTEM-COMPUTED "NOT ALTERED since your rejection" badge (+ the
|
|
115
|
+
saved rejection note); a differing fingerprint shows a subtle "changed since
|
|
116
|
+
last review" hint. Judged by hashing, never by an operating agent.
|
|
117
|
+
- DEVICE-SPLIT APPROVALS: `devices?: ("pc"|"mobile")[]` (default pc). One
|
|
118
|
+
approve button per required device; the item counts approved and advances
|
|
119
|
+
only when EVERY required device is approved (reject stays whole-item).
|
|
120
|
+
Plain historical approvals are GRANDFATHERED as fully approved; the round
|
|
121
|
+
recomputes device-aware on load. Detected device gets the primary-styled
|
|
122
|
+
hint button (all stay clickable) with per-device tooltips.
|
|
123
|
+
- IMMEDIATE JOURNEY NAV: no "page complete" interstitial - finishing a page
|
|
124
|
+
navigates instantly to the next pending page; the completion panel shows
|
|
125
|
+
only when the WHOLE journey is clean.
|
|
126
|
+
- SUB-HIGHLIGHT: `highlightWords?: string[]` wraps matching words/phrases
|
|
127
|
+
inside the spotlighted element with a secondary mark.
|
|
128
|
+
- CODENAMES + COPY-REF: deterministic two-word codename per (target, itemId)
|
|
129
|
+
shown on the card with a "Copy ref" button; `codenameFor`/`findByCodename`
|
|
130
|
+
resolver exported (client + server) and codenames attached to state GET
|
|
131
|
+
responses.
|
|
132
|
+
- MINIMIZE BUBBLE: replaces the 3s peek - the panel collapses to a draggable
|
|
133
|
+
floating bubble (mouse + touch pointer events); tap restores. Panel drag is
|
|
134
|
+
pointer-based now too. Note textarea stretches to fill the card. Tooltips
|
|
135
|
+
on every icon/abbreviation.
|
|
136
|
+
- Server: migration 2 adds `fp`, `approved_pc`, `approved_mobile` to
|
|
137
|
+
qa_review_state; state POST accepts `fp` + `approvedDevices`.
|
|
138
|
+
|
|
139
|
+
## 0.2.0 - 2026-07-21
|
|
140
|
+
|
|
141
|
+
- JOURNEY: cross-page review flow. `journey` prop (ordered pages with path /
|
|
142
|
+
target / label / itemIds): when a page's round is done the finish panel shows
|
|
143
|
+
per-page pending counts (one state GET per target; failed fetch = page counts
|
|
144
|
+
as pending) and auto-navigates (3s, cancellable) to the next page with
|
|
145
|
+
pending items - wrapping past the end, never the current page. Activation
|
|
146
|
+
query params (gate param, auth key) are preserved on the hop; the per-page
|
|
147
|
+
`target` override is dropped. Prev at the first item goes back to the
|
|
148
|
+
previous journey page; Next past the last item opens the journey summary.
|
|
149
|
+
Header shows "page X/N" progress.
|
|
150
|
+
- TASK ITEMS: `selector` is now optional - an item without one renders as a
|
|
151
|
+
centered card (no spotlight, full-page dim) with an optional `action` link
|
|
152
|
+
button (new tab). Same approve/reject/note/undo + real-time persistence.
|
|
153
|
+
|
|
154
|
+
## 0.1.1 - 2026-07-21
|
|
155
|
+
|
|
156
|
+
- REAL-TIME persistence unified across consumers (SSoT fix): every
|
|
157
|
+
approve/reject, note change (debounced, default 600ms), and undo writes to
|
|
158
|
+
the server ledger immediately; the session-snapshot submit is optional
|
|
159
|
+
either way.
|
|
160
|
+
- Write-behind offline resilience: failed server writes queue in localStorage
|
|
161
|
+
(survive reloads), replay in order on the next action, and hydration replays
|
|
162
|
+
still-pending ops over the server map so an offline verdict is never
|
|
163
|
+
clobbered or lost silently.
|
|
164
|
+
- Finish panel unified: the auto-saved note always shows and the copy button
|
|
165
|
+
is canonically labeled "Copy JSON" in all configurations.
|
|
166
|
+
|
|
167
|
+
## 0.1.0 - 2026-07-21
|
|
168
|
+
|
|
169
|
+
- Initial release: spotlight QA review overlay (round-based review frozen at
|
|
170
|
+
load, fixed denominator, undo, 3s peek, element-picker notes, design
|
|
171
|
+
variations, keyboard driving), client verdict store (localStorage cache +
|
|
172
|
+
durable server mirror, single-item invalidation, no reset-all), server
|
|
173
|
+
handler factory (state ledger, session submit, sessions list, access probe,
|
|
174
|
+
BYO `authorize`), self-provisioning Postgres storage with `site` scoping.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arty McLabin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# qa-review
|
|
2
|
+
|
|
3
|
+
Interactive on-page QA review for React apps: a spotlight walkthrough overlay
|
|
4
|
+
that steps a reviewer through the elements of a real rendered page
|
|
5
|
+
(approve / reject / note / undo / design-variation picking), plus a durable
|
|
6
|
+
server-side verdict ledger with a self-provisioning Postgres store.
|
|
7
|
+
|
|
8
|
+
Built for "founder reviews the page element by element" workflows: the reviewer
|
|
9
|
+
opens the live page, the overlay dims everything except the current item, and
|
|
10
|
+
every verdict is persisted immediately - refreshes, storage wipes, and device
|
|
11
|
+
switches never lose progress.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Spotlight walkthrough** - dims the page except the current `[data-qa]`
|
|
16
|
+
target; card shows title/subtitle + Approve/Reject/Prev/Next.
|
|
17
|
+
- **Round-based review** - the set of actionable (not-yet-approved) items is
|
|
18
|
+
frozen at load with a fixed denominator ("1 of N"), so approved items never
|
|
19
|
+
re-appear mid-round.
|
|
20
|
+
- **Durable verdict ledger** - verdicts are saved per item as you go
|
|
21
|
+
(localStorage cache + server ledger). No bulk reset exists; a single item is
|
|
22
|
+
re-queued by invalidating just it (`verdict: null`).
|
|
23
|
+
- **Undo, Peek (3s clean-page view), element-picker notes, keyboard driving**
|
|
24
|
+
(A/R/arrows/U/Esc), draggable panel.
|
|
25
|
+
- **Design variations** - an item can expose N live-swappable variants; the
|
|
26
|
+
chosen variant is recorded with the approval.
|
|
27
|
+
- **Session snapshots** - optional "Save review to database" submit that stores
|
|
28
|
+
the full run (summary + per-item results) for auditability.
|
|
29
|
+
- **BYO auth** - the server handlers take an `authorize(req)` callback; plug in
|
|
30
|
+
any gate (SSO, password, none for local tools). Fail-closed.
|
|
31
|
+
- **Self-provisioning Postgres storage** - the adapter creates its own
|
|
32
|
+
`qa_review_*` tables on first use (versioned, advisory-locked). A `site`
|
|
33
|
+
scope column lets one database serve many installs.
|
|
34
|
+
- **Cross-page journey** - an ordered multi-page review: finishing a page
|
|
35
|
+
navigates immediately to the next page with pending items (activation query
|
|
36
|
+
params survive the hop); a completion panel shows only when the whole
|
|
37
|
+
journey is clean.
|
|
38
|
+
- **Task items** - selectorless items render as a centered card with an
|
|
39
|
+
optional action-link button, for visit-this-page checks and decisions.
|
|
40
|
+
- **NOT-ALTERED poka-yoke** - every verdict stores a content fingerprint; a
|
|
41
|
+
re-shown rejected item whose content still hashes identical gets a
|
|
42
|
+
system-computed "NOT ALTERED since your rejection" badge.
|
|
43
|
+
- **Device-split approvals** - items can require per-device sign-off
|
|
44
|
+
(PC/mobile); approved only when every required device approved. Plain
|
|
45
|
+
historical approvals are grandfathered as fully approved.
|
|
46
|
+
- **Sub-highlights** - `highlightWords` marks specific words inside the
|
|
47
|
+
spotlighted element, replacing "where to look" prose.
|
|
48
|
+
- **Codenames** - a deterministic two-word codename per item ("red-apple")
|
|
49
|
+
with a Copy-ref button and an exported resolver, so humans and agents can
|
|
50
|
+
reference items by name. Included in state GET responses.
|
|
51
|
+
- **Minimize bubble** - the panel collapses to a draggable floating bubble
|
|
52
|
+
(mouse + touch); tap to restore.
|
|
53
|
+
- **No CSS toolchain required** - the overlay injects its own stylesheet;
|
|
54
|
+
brand colors come from a small theme prop.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install @artymclabin/qa-review
|
|
60
|
+
# or
|
|
61
|
+
pnpm add @artymclabin/qa-review
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Install straight from git if you want an unreleased commit:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install github:ArtyMcLabin/qa-review
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For CI environments without registry access, vendor a tarball:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# in this repo
|
|
74
|
+
npm pack # -> artymclabin-qa-review-<version>.tgz
|
|
75
|
+
# in the consumer
|
|
76
|
+
pnpm add ./vendor/artymclabin-qa-review-<version>.tgz
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Peer dependencies: `react`, `react-dom`, `lucide-react`.
|
|
80
|
+
|
|
81
|
+
## Quick start (Next.js App Router)
|
|
82
|
+
|
|
83
|
+
### 1. Server: mount the handlers
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// src/lib/qa-review.ts
|
|
87
|
+
import { createQAReviewHandlers } from "@artymclabin/qa-review/server";
|
|
88
|
+
|
|
89
|
+
export const qaHandlers = createQAReviewHandlers({
|
|
90
|
+
site: "example-site", // scope for this install (one DB can serve many)
|
|
91
|
+
authorize: async (req) => {
|
|
92
|
+
const user = await verifyMySession(req); // your gate; null -> 401
|
|
93
|
+
return user ? { reviewer: user.name, displayName: user.name } : null;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// src/app/api/qa/state/route.ts
|
|
100
|
+
import { qaHandlers } from "@/lib/qa-review";
|
|
101
|
+
export const runtime = "nodejs";
|
|
102
|
+
export const dynamic = "force-dynamic";
|
|
103
|
+
export const GET = qaHandlers.stateGET;
|
|
104
|
+
export const POST = qaHandlers.statePOST;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Mount `submitPOST`, `sessionsGET`, and `accessGET` the same way on their own
|
|
108
|
+
routes as needed.
|
|
109
|
+
|
|
110
|
+
### 2. Database
|
|
111
|
+
|
|
112
|
+
Set the connection string (any Postgres - local, Neon, Supabase, RDS):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
QA_REVIEW_DATABASE_URL=postgresql://user:password@db.example.com/mydb
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
No migrations to write: on the first request the adapter provisions
|
|
119
|
+
`qa_review_state` (the per-item ledger), `qa_review_sessions` (session
|
|
120
|
+
snapshots), and `qa_review_migrations` (its own version bookkeeping).
|
|
121
|
+
|
|
122
|
+
### 3. Client: mark targets and mount the overlay
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// Any element you want reviewed gets a stable [data-qa] anchor:
|
|
126
|
+
<section data-qa="hero">...</section>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
"use client";
|
|
131
|
+
import { QAReviewOverlay } from "@artymclabin/qa-review";
|
|
132
|
+
|
|
133
|
+
const ITEMS = [
|
|
134
|
+
{ id: "hero", title: "Hero headline", selector: '[data-qa="hero"]' },
|
|
135
|
+
{ id: "pricing", title: "Pricing table", sub: "Check the currency.", selector: '[data-qa="pricing"]' },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
export function PageQA() {
|
|
139
|
+
return (
|
|
140
|
+
<QAReviewOverlay
|
|
141
|
+
items={ITEMS}
|
|
142
|
+
target="example-site:/pricing" // one ledger bucket per reviewed surface
|
|
143
|
+
gateParam="qaReview" // omit if activation is gated upstream
|
|
144
|
+
submitUrl="/api/qa/submit" // omit to hide the session-save button
|
|
145
|
+
theme={{ accent: "#ffde4d" }}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## API sketch
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
// client
|
|
155
|
+
import {
|
|
156
|
+
QAReviewOverlay, // the overlay component
|
|
157
|
+
createQAStore, // per-target verdict store (localStorage + server mirror)
|
|
158
|
+
targetFromLocation, // ?target= override helper (E2E isolation)
|
|
159
|
+
} from "@artymclabin/qa-review";
|
|
160
|
+
|
|
161
|
+
// server
|
|
162
|
+
import {
|
|
163
|
+
createQAReviewHandlers, // { stateGET, statePOST, submitPOST, sessionsGET, accessGET }
|
|
164
|
+
createPostgresStorage, // default storage; implement QAReviewStorage to swap
|
|
165
|
+
} from "@artymclabin/qa-review/server";
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
State endpoint semantics (the ledger):
|
|
169
|
+
|
|
170
|
+
- `GET ?target=...` -> `{ ok, verdicts: { itemId: { verdict, note, variant } } }`
|
|
171
|
+
- `POST { target, itemId, verdict | note | variant }` -> merge-upsert one item
|
|
172
|
+
- `POST { target, itemId, verdict: null }` -> delete that one item
|
|
173
|
+
(re-queues it on the next round). There is deliberately **no reset-all**.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { QAReviewItem, QATheme } from "./types.js";
|
|
3
|
+
import { type QAJourneyConfig } from "./journey.js";
|
|
4
|
+
/** Was the pointer gesture a click (vs a drag)? Exported for tests. */
|
|
5
|
+
export declare function isClickGesture(dx: number, dy: number): boolean;
|
|
6
|
+
/** Note-reference text for a picked element: « label » (truncated) or tag. */
|
|
7
|
+
export declare function pickedElementRef(textContent: string | null, tagName: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Element-pick mode semantics (0.3.1): LEFT click picks and exits the mode;
|
|
10
|
+
* RIGHT click picks and STAYS for multi-select (context menu suppressed).
|
|
11
|
+
*/
|
|
12
|
+
export declare function shouldExitPickMode(button: "left" | "right"): boolean;
|
|
13
|
+
/** Viewport width at/below which the panel auto-minimizes to the bubble. */
|
|
14
|
+
export declare const AUTO_BUBBLE_MAX_WIDTH_PX = 767;
|
|
15
|
+
export type BubbleEvent = {
|
|
16
|
+
type: "viewport";
|
|
17
|
+
mobile: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
type: "manual";
|
|
20
|
+
minimized: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Auto-bubble state machine (0.3.1): a VIEWPORT switch always applies its
|
|
24
|
+
* auto state (mobile-ish width -> bubble, desktop -> panel); a MANUAL
|
|
25
|
+
* minimize/restore wins immediately and holds until the NEXT viewport switch.
|
|
26
|
+
* Exported for tests.
|
|
27
|
+
*/
|
|
28
|
+
export declare function nextMinimized(current: boolean, ev: BubbleEvent): boolean;
|
|
29
|
+
export interface QAReviewOverlayProps {
|
|
30
|
+
items: QAReviewItem[];
|
|
31
|
+
/** Review bucket - persisted with every verdict (e.g. "example-site:/pricing"). */
|
|
32
|
+
target: string;
|
|
33
|
+
/**
|
|
34
|
+
* URL param that must be present to activate the overlay (e.g. "qaReview").
|
|
35
|
+
* Omit when activation is handled upstream (auth-gated lazy mount): the
|
|
36
|
+
* overlay is then active immediately on mount.
|
|
37
|
+
*/
|
|
38
|
+
gateParam?: string;
|
|
39
|
+
/** State-ledger endpoint. Default "/api/qa/state". */
|
|
40
|
+
stateUrl?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Session-snapshot endpoint (e.g. "/api/qa/submit"). When set, the finish
|
|
43
|
+
* panel shows a "Save review to database" button POSTing the full run there.
|
|
44
|
+
* The snapshot is OPTIONAL either way: per-item verdicts always persist to
|
|
45
|
+
* the state ledger in real time as they are decided.
|
|
46
|
+
*/
|
|
47
|
+
submitUrl?: string;
|
|
48
|
+
/** localStorage key builder. Default: `qa-review-verdicts:<target>`. */
|
|
49
|
+
storageKey?: (target: string) => string;
|
|
50
|
+
/** Brand colors for the overlay chrome (defaults are a dark yellow theme). */
|
|
51
|
+
theme?: Partial<QATheme>;
|
|
52
|
+
/**
|
|
53
|
+
* Cross-page review journey (ordered pages with their ledger targets +
|
|
54
|
+
* item ids). The page whose `target` equals this overlay's `target` is the
|
|
55
|
+
* current journey position.
|
|
56
|
+
*/
|
|
57
|
+
journey?: QAJourneyConfig;
|
|
58
|
+
}
|
|
59
|
+
export declare function QAReviewOverlay({ items, target, gateParam, stateUrl, submitUrl, storageKey, theme, journey, }: QAReviewOverlayProps): React.ReactPortal | null;
|