qapture2 0.9.1 → 0.10.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.
package/README.md CHANGED
@@ -14,14 +14,9 @@ npm install qapture2
14
14
 
15
15
  ## Contents
16
16
 
17
- - [What's new in v0.7 "Walk"](#whats-new-in-v07-walk)
18
- - [What's new in v0.6 "Elbow Room"](#whats-new-in-v06-elbow-room)
19
- - [What's new in v0.5 "Loop"](#whats-new-in-v05-loop)
20
- - [What's new in v0.4 "Ledger"](#whats-new-in-v04-ledger)
21
- - [Breaking Changes (v0.3.0 "Graphite")](#breaking-changes-v030-graphite)
22
17
  - [Quick Start](#quick-start)
23
18
  - [Steps to reproduce, recorded for you](#steps-to-reproduce-recorded-for-you)
24
- - [Screenshots: two engines](#screenshots-two-engines)
19
+ - [Screenshots: three engines](#screenshots-three-engines)
25
20
  - [Saving to a folder](#saving-to-a-folder)
26
21
  - [Where notes live, and what "storage full" means](#where-notes-live-and-what-storage-full-means)
27
22
  - [Config Reference](#config-reference)
@@ -40,98 +35,10 @@ npm install qapture2
40
35
 
41
36
  ---
42
37
 
43
- ## What's new in v0.7 "Walk"
44
38
 
45
- | | |
46
- |---|---|
47
- | **The Walk** | One guided sequence over either your test plan or your notes, with a **Take me there** button that actually navigates. Pressing a Guide step now walks from it. |
48
- | **Walk the re-test queue** | Filter to Re-test, press Walk: each stop takes you to the page, lights up the spot, re-shoots it and takes your verdict. |
49
- | **A link that starts it** | `?qa=walk:retest` opens the app with the re-test round already running — send that instead of instructions. |
50
- | **"Doesn't apply"** | A third grade for plan steps that aren't relevant to this build. Removed from coverage totals rather than counted either way. |
51
- | **Alt+1/2/3** | Set severity while typing a note. |
52
- | **Session summary** | The export leads with "12 points · 5 bugs · 4 pages · over 38 minutes". |
53
-
54
- **Two fixes from real use:** the widget could be hidden underneath an app's own high-z-index chrome (it now sits above everything, in its own stacking context, without ever swallowing a click), or removed outright by a framework clearing `<body>` (it now puts itself back). And the panel's open state, tab and walk position survive a reload — the notes always did, the *place in the work* didn't.
55
-
56
- ---
57
-
58
- ## What's new in v0.6 "Elbow Room"
59
-
60
- | | |
61
- |---|---|
62
- | **Bulk actions** | Select many notes → mark them Open / Re-test / Verified, or delete them, in one pass with a single undo. |
63
- | **Compact list** | One line per note, expanding in place — for when a session has thirty of them. |
64
- | **Move the panel** | Dock it to the other edge, or collapse it to its header strip, so it stops covering the app you're testing. |
65
- | **Whole-screen capture** | Grab everything visible without dragging a box across the screen. |
66
-
67
- ---
68
-
69
- ## What's new in v0.5 "Loop"
70
-
71
- Not a breaking release. Notes from 0.3.x and 0.4.x read back unchanged.
72
-
73
- | | |
74
- |---|---|
75
- | **Steps to reproduce, written for you** | Every note now carries what the tester clicked, typed into, toggled and navigated on the way to it. Never what they typed — see [below](#steps-to-reproduce-recorded-for-you). |
76
- | **Draw on the screenshot** | Tap the shot, add an arrow / box / pen mark in one of four colours. Burned into the image, so it survives everywhere the screenshot goes. Never interrupts the capture flow. |
77
- | **A capture shortcut** | `Alt+Shift+C` (`Option+Shift+C` on macOS) jumps straight into capture from anywhere; press again to back out. Configurable via `captureHotkey`. |
78
- | **A re-test queue** | Note status is now Open → **Re-test** → Verified. "Re-test" is the state that tells a tester what to check on the next build, with a filter chip and a header badge so the queue can't be missed. |
79
- | **Automatic backups** | A backup ZIP downloads every 5 notes, for everyone who can't use folder saving (Safari, Firefox, phones). Pauses itself while folder saving is on. |
80
- | **Catches what you miss** | When the page throws an error or a request fails, Qapture offers a one-tap capture with the error already written in — the bug nobody reported because nobody saw it. |
81
- | **Before / after on a re-test** | A note in the re-test queue gets a "Re-test now" button that re-shoots the same target, so "is it actually fixed?" is answered with a picture. |
82
- | **Share (phones)** | Hands the campaign ZIP to the OS share sheet — WhatsApp, Mail, Files, AirDrop — because a "download" on a phone lands where nobody finds it. |
83
- | **A welcome card** | Three lines, shown once, for someone who was just handed a beta link. |
84
-
85
- ---
86
-
87
- ## What's new in v0.4 "Ledger"
88
-
89
- v0.4 is **not** a breaking release — every 0.3.x config, note and export keeps
90
- working, and each new feature is off until someone turns it on.
91
-
92
- | | |
93
- |---|---|
94
- | **Screenshots frame the right thing** | Capture mode's own scroll lock used `overflow:hidden`, which **unstuck every `position: sticky` header** just before the screenshot was rendered — measured at 20px of wrongness in a 40px capture, now 0.0px. See [Screenshots: two engines](#screenshots-two-engines). |
95
- | **Pixel-exact capture (opt-in)** | A real photograph rather than a redraw — so canvas/WebGL, video, cross-origin iframes and exotic CSS all come out right. Taken the instant capture opens, then the screen is handed straight back: one frame per capture, nothing recording in between. Desktop browsers. |
96
- | **Save straight to a folder** | Pick a QA folder once; every note is written to disk as it's saved, organised `Project / Campaign / notes + screenshots + REPORT.md`. See [Saving to a folder](#saving-to-a-folder). |
97
- | **Storage that explains itself** | A real usage meter, WebP screenshots (~10× smaller), a request to stop the browser evicting your data, and a "drop screenshots, keep findings" recovery valve. |
98
- | **A usable notes list** | Severity/status filter chips with counts, text search, and a "this page" toggle. |
99
- | **Simple mode** | Hides Logins and Guide for a tester who was just handed a link. |
100
- | **Minimized capture** | A small box next to your selection instead of the full card — type, Enter, move on. |
101
-
102
- ---
103
39
 
104
- ## Breaking Changes (v0.3.0 "Graphite")
105
40
 
106
- **v0.3.0 is a breaking release.**
107
41
 
108
- - **Custom themes are removed.** The widget now ships one fixed, self-contained
109
- dark design ("Graphite") — there is no more per-project colour override. A
110
- `theme` key left in `qa.config` is no longer read; `validateConfig` ignores
111
- it and pushes this exact warning:
112
-
113
- > theme: custom themes were removed in Qapture 0.3.0 — the widget now ships
114
- > one fixed, self-contained design. The "theme" key is ignored; remove it
115
- > from your qa.config to silence this warning.
116
-
117
- Delete the `theme` block from your config — see the updated
118
- [`examples/minimal.config.ts`](./examples/minimal.config.ts) and
119
- [`examples/stitch-and-sell.config.ts`](./examples/stitch-and-sell.config.ts),
120
- both of which had their `theme` block removed entirely. The `QaTheme` type
121
- is still exported so old config objects keep type-checking, but it is
122
- `@deprecated` and has no runtime effect — see [`QaTheme`](#qatheme-deprecated).
123
- - **New:** a guided, step-by-step walkthrough over your journey ("test-along"),
124
- severity + status on notes, a one-click "Copy as agent prompt", and an
125
- undo-capable delete/clear system. See the sections below.
126
- - **New, and worth reading if you care about privacy:** an (on-by-default,
127
- opt-out) runtime context capture that records recent console errors and
128
- failed network calls into each note. See
129
- [Runtime Context Capture](#runtime-context-capture) and
130
- [SECURITY.md § Runtime context capture](./SECURITY.md#runtime-context-capture).
131
-
132
- Full details: [CHANGELOG.md](./CHANGELOG.md#030-graphite--unreleased).
133
-
134
- ---
135
42
 
136
43
  ## Quick Start
137
44
 
@@ -265,168 +172,124 @@ capture — no listener is installed at all. The complete guarantees are in
265
172
 
266
173
  ---
267
174
 
268
- ## Screenshots: two engines
175
+ ## Screenshots: three engines
176
+
177
+ Every engine crops the exact viewport rectangle you selected. They differ in
178
+ where the pixels come from, and that difference is the whole story.
269
179
 
270
- Qapture can produce a screenshot two different ways. Both crop the exact
271
- viewport rectangle you selected; they differ in where the pixels come from.
180
+ | | Prompts you | Real pixels | Works in |
181
+ |---|---|---|---|
182
+ | **`native`** — local helper | never | yes | any browser, macOS |
183
+ | **`exact`** — browser screen capture | every capture | yes | Chromium, Safari, Firefox |
184
+ | **`dom`** — html2canvas redraw | never | no, a reconstruction | everywhere |
272
185
 
273
- ### `dom` the default, works everywhere
186
+ Qapture picks the best one available and tells you in the capture card which
187
+ one it used.
274
188
 
275
- html2canvas **re-renders a clone** of your DOM into an offscreen frame and
276
- rasterises it. No permission prompt, works in every browser, and it is the
277
- only option on Firefox, Safari and mobile.
189
+ ### `native` real screenshots, no prompt (recommended, macOS)
278
190
 
279
- Because it is a reconstruction rather than a photograph, it has limits worth
280
- knowing:
191
+ Run this next to your dev server and leave it going:
192
+
193
+ ```bash
194
+ npx qapture2 shots
195
+ ```
196
+
197
+ It drives `/usr/sbin/screencapture` — the binary behind Cmd+Shift+4. macOS
198
+ asks the terminal for Screen Recording permission **once**, and never again.
199
+
200
+ That single fact is why this engine exists. `getDisplayMedia`, which the
201
+ `exact` engine uses, prompts on *every* call by design; no flag or prior grant
202
+ changes it. Each prompt restarts the OS capture pipeline, which on a laptop is
203
+ heat and a machine that feels like it is recording continuously. The helper
204
+ takes a picture and exits — nothing is left running between shots.
205
+
206
+ The widget finds it automatically. Nothing to configure, and nothing to turn
207
+ on in Settings, because there is no prompt to opt into.
208
+
209
+ **What it will answer.** It binds to `127.0.0.1` only, so nothing off your
210
+ machine can reach it. It answers `http://localhost:*` and `http://127.0.0.1:*`
211
+ without being asked. Any other origin — a staging site you are testing
212
+ against, say — must be named explicitly:
213
+
214
+ ```bash
215
+ npx qapture2 shots --allow https://staging.example.com
216
+ ```
217
+
218
+ Origins not on that list get no CORS headers at all, so the browser blocks the
219
+ request before the page sees a reply. Add `--port 7018` (and `shotPort: 7018`
220
+ in your config) if 7017 is taken.
221
+
222
+ **If captures come back as wallpaper with no windows in them**, the permission
223
+ was not granted: System Settings → Privacy & Security → Screen Recording, tick
224
+ your terminal, restart it.
225
+
226
+ ### `exact` — real pixels, at a prompt per screenful
227
+
228
+ The browser's own Screen Capture API. Turn it on from the capture hint bar
229
+ ("Pixel-exact shots") or Settings; turning it on does not prompt, the first
230
+ capture does.
231
+
232
+ One grant covers several notes: "Save + next" (⌘/Ctrl + Shift + Enter) files
233
+ the note and puts you straight back to framing the same photograph. Worth
234
+ knowing in Safari, where the per-site Screen Sharing setting offers only *Ask*
235
+ and *Deny* — there is no *Allow*, so the only lever is needing fewer captures.
236
+
237
+ Chromium shares this tab directly (`preferCurrentTab`), so the frame *is* the
238
+ viewport — measured at 0.0px error. Safari and Firefox can only share a window
239
+ or a screen; see below for how the page is found inside that.
240
+
241
+ ### `dom` — the fallback, works everywhere
242
+
243
+ html2canvas re-renders a clone of your DOM and rasterises it. No prompt, every
244
+ browser, and the only option on mobile. Because it is a reconstruction rather
245
+ than a photograph:
281
246
 
282
247
  - `<canvas>` / WebGL, `<video>` and cross-origin `<iframe>` content cannot be
283
- read and render blank or approximated;
248
+ read, and come out blank or approximated;
284
249
  - CSS the cloner doesn't implement (some `backdrop-filter`, `mask`, exotic
285
250
  gradients) renders differently;
286
- - anything the clone lays out differently is a shot that doesn't match.
287
-
288
- That last category is what v0.4 fixed. The culprit was Qapture's own scroll
289
- lock: freezing the page with `overflow: hidden` on `<html>` takes away the
290
- scrollport that `position: sticky` elements stick to, so every stuck header
291
- and toolbar jumped back to its natural document position in the instant
292
- between you choosing a rectangle and the screenshot being rendered. The lock
293
- now swallows scroll events instead of touching CSS, and stuck elements are
294
- additionally pinned in html2canvas's clone (it doesn't implement sticky
295
- either).
296
-
297
- You can measure this yourself `npm run capture-accuracy-test` captures a
298
- rectangle straddling a colour boundary and reports the error in pixels. It
299
- reads 0.0px on 0.4.0 and 20px (of a 40px capture) on 0.3.1.
300
-
301
- **v0.7.1 fixed a second, much louder failure: no screenshot at all.**
302
- html2canvas ships its own CSS colour parser, and it predates CSS Color 4.
303
- Handed `oklch(...)` it throws, and the throw aborts the whole render — so the
304
- tester saw "Screenshot failed" and a Retry that re-ran the identical render and
305
- could never succeed. Tailwind v4 emits `oklch()` for its entire default
306
- palette and shadcn/ui inherits it, so on those stacks screenshots never worked
307
- at all, on any page. `lab()`, `lch()`, `oklab()`, `color()` and `color-mix()`
308
- failed the same way.
309
-
310
- Qapture now rewrites those colours to plain sRGB inside the clone html2canvas
311
- renders never in your page. The conversion paints each colour onto a 1×1
312
- canvas and reads the pixel back rather than reading `fillStyle` as a string,
313
- because Chrome round-trips `oklch(...)` unchanged. Gradients and shadows are
314
- rewritten in place, and a render that still throws is retried once with
315
- decoration stripped. `npm run modern-css-test` proves it, and proves itself:
316
- it first asserts that raw html2canvas *still* dies on the same fixture.
317
-
318
- **v0.7.2 fixed the last of the mis-framing: clicking an element only captured
319
- the part of it that was on screen.** Dragged regions are clamped into the
320
- viewport before capture, so they were always safe but an element *pick* is a
321
- raw `getBoundingClientRect()`, and a table column, a sidebar, a long form or a
322
- wide toolbar routinely extends past the fold. You got a fragment. And when the
323
- element started *above* the viewport, the crop slid down the page to fill its
324
- height, so the screenshot was the right size showing the wrong content — a
325
- failure with nothing on screen to reveal it.
326
-
327
- Captures now render the union of the viewport and the selection, so an element
328
- that leaves the screen in any direction is rendered whole, up to 4000px per
329
- side (past a ~64 MB device-pixel render budget the scale drops, not the
330
- framing). The exact engine can't photograph off-screen pixels, so there the
331
- selection is trimmed to the visible part instead — cropped, never displaced.
332
- `npm run element-capture-test` measures all four cases.
333
-
334
- **v0.7.3 corrected the half of that which was wrong.** Chasing an element's
335
- whole box is only right when the whole box is *drawn*. Inside a scroll
336
- container it isn't: a dashboard scrolls an inner `overflow:auto` box, so a
337
- 3000px column inside a 700px box is 700px of pixels and 2300px of nothing.
338
- 0.7.2 rendered the nothing — measured at 23.3% element, 76.7% empty page, with
339
- the real content squeezed from 1040px wide to 312px by the long-edge cap.
340
-
341
- Captures are now clipped to what the browser actually paints: the element's box
342
- intersected with every ancestor that clips it, on either axis, up to `<html>`.
343
- The viewport is deliberately not a clipper, so a long form on an ordinary
344
- scrolling page still captures in full. Same fixture after: 100% element, full
345
- 1040×1400. The hover outline is clipped identically, so what you highlight is
346
- what you get.
347
-
348
- ### "The screenshot looks simulated"
349
-
350
- Because by default it is. The DOM engine re-draws your page; fonts shift,
351
- shadows and gradients flatten, `<canvas>` and video come out blank. From
352
- v0.7.3 a redrawn capture says so under the preview and offers a one-tap **Use
353
- real screenshots**, which grants tab-share and re-shoots the same selection
354
- with the exact engine below.
355
-
356
- **From v0.7.6 this works in Safari and Firefox too.** They have no tab capture,
357
- but they can share a *window*, and Qapture works out where your page sits
358
- inside that frame — by measuring, not by guessing at toolbar heights. On the
359
- first capture the page is covered for a fifth of a second by a dark card with
360
- four coloured corners; the engine photographs it, finds the corners and solves
361
- for the mapping. That dark flash is the measurement. If it can't verify the
362
- result it refuses the grant and falls back to redrawing, because a screenshot
363
- of the wrong pixels is worse than no screenshot.
364
-
365
- Chromium still takes the direct path (`preferCurrentTab`), unchanged and still
366
- 0.0px. Only iOS/iPadOS have no Screen Capture API at all, and there the card
367
- says so.
368
-
369
- **If your app draws anything with inline SVG, a `<canvas>`, or video, turn real
370
- screenshots on.** A redraw will render an SVG chart as bare outlines and a
371
- canvas as a blank box, every time, in any tool built this way.
372
-
373
- ### `exact` — opt-in, pixel-for-pixel
374
-
375
- Uses the Screen Capture API to photograph **the viewport's real composited
376
- pixels**, then crops your rectangle out arithmetically. Nothing is
377
- re-rendered, so it cannot mis-frame, and everything above renders correctly
378
- because it was never re-drawn in the first place.
379
-
380
- **The photograph is taken when you open capture mode, not when you finish
381
- dragging.** That ordering is the whole design, and it decides three things at
382
- once:
383
-
384
- - **Nothing keeps recording.** The stream is acquired, one frame is taken, and
385
- the track is stopped — about a third of a second — so the browser's sharing
386
- indicator blinks and goes. Before 0.8 the stream was held for the entire
387
- session so it would only prompt once, which on Safari (where the only option
388
- is sharing a window or a whole screen) meant the indicator stayed lit and the
389
- capture pipeline kept running behind every page. One prompt per capture is
390
- the price of not recording you between them.
391
- - **You crop what you can see.** The still is shown under the capture scrim
392
- while you frame, so a page that animates cannot move between the moment you
393
- point at something and the moment it is cropped — and a hover state, an open
394
- dropdown or a tooltip survives being framed instead of being dismissed by the
395
- mouse moving to start the drag.
396
- - **Whether this shot is a photograph or a redraw is settled before you
397
- start.** A still is either held or it is not. Previously the exact engine
398
- reached for a frame at the end, from a stream that could quietly have died,
399
- and fell through to `dom` without saying so — which is why the same click
400
- could photograph one time and redraw the next.
401
-
402
- Everything else:
403
-
404
- - **Turn it on** from the capture hint bar ("Pixel-exact shots") or Settings.
405
- Turning it on does not prompt; the first capture does.
406
- - **One prompt can cover several notes.** "Save + next" on the annotation card
407
- (⌘/Ctrl + Shift + Enter) files the note and puts you back to framing the same
408
- photograph. Worth knowing in **Safari**, where the per-site Screen Sharing
409
- setting offers only *Ask* and *Deny* — there is no *Allow*, so every capture
410
- prompts and the only lever is needing fewer captures. Three bugs on one
411
- screen, one prompt.
412
- - Nothing leaves the device — the frame is cropped locally and never uploaded.
251
+ - an element inside a scroll container is clipped to what is actually painted,
252
+ because the rest of it does not exist as pixels anywhere.
253
+
254
+ If a shot looks simulated rather than photographed, this is the engine that
255
+ took it start the helper above and it will not be.
256
+
257
+ ### How a photograph is aligned to the page
258
+
259
+ `native` and `exact`-on-Safari/Firefox both hand back a frame containing the
260
+ browser's toolbar, and maybe a whole desktop. Working out where the page sits
261
+ inside that by arithmetic (`outerHeight - innerHeight`, `screenX`,
262
+ `devicePixelRatio`) is a stack of guesses, and a wrong guess is a screenshot
263
+ confidently showing the *wrong pixels* worse than no screenshot, because
264
+ nobody double-checks one that looks fine.
265
+
266
+ So Qapture measures instead. It covers the page with an opaque card carrying
267
+ four known colours at four known corners, photographs that, and solves for
268
+ scale and origin from where the colours landed. Toolbar height, pixel ratio
269
+ and monitor layout never enter the arithmetic, so they cannot be wrong in it.
270
+ You see a dark flash for a fifth of a second.
271
+
272
+ Two corners solve the mapping and the other two verify it. **A calibration
273
+ that cannot be verified is refused** — the frame is dropped and the capture
274
+ falls back to `dom`, visibly, rather than returning a confidently wrong image.
275
+
276
+ On the `native` path the measurement is cached against the window's geometry,
277
+ so that flash happens once a session rather than once a capture.
278
+
279
+ ### Common to both photograph engines
280
+
281
+ - **The picture is taken when you enter capture mode, not when you finish
282
+ dragging.** You then crop a frozen still, so the page cannot move under you,
283
+ and a hover state, an open dropdown or a tooltip survives being framed
284
+ instead of being dismissed by the mouse moving to start the drag.
285
+ - Nothing leaves the device. The frame is cropped locally and never uploaded.
413
286
  - The QA overlay is hidden for the captured frame, so the scrim, the selection
414
287
  outline and the annotation card never appear in the image.
415
- - **Chromium** shares this tab directly (`preferCurrentTab`), so the frame *is*
416
- the viewport — measured at 0.0px error.
417
- - **Safari and Firefox** have no tab capture, only a window or a screen. That
418
- frame does contain the page, and Qapture finds it by *measuring*: it covers
419
- the page with an opaque card carrying four known colours at four known
420
- corners, photographs that, and solves for scale and origin from where the
421
- colours landed. Toolbar height, pixel ratio and monitor layout all cancel
422
- out. You see a dark flash for a fifth of a second. Two corners solve and the
423
- other two verify — and a calibration that cannot be verified is **refused**,
424
- falling back to `dom` rather than returning a confidently wrong image.
425
288
  - The still belongs to the viewport it was taken in. Resize the window
426
- mid-capture and the crop is refused for the same reason.
289
+ mid-capture and the crop is refused rather than guessed.
427
290
  - Off-screen pixels do not exist in a photograph at any price, so a selection
428
- that runs past the fold is trimmed to what was visible. The `dom` engine
429
- re-renders and has no such limit.
291
+ running past the fold is trimmed to what was visible. `dom` re-renders and
292
+ has no such limit.
430
293
 
431
294
  ---
432
295
 
@@ -531,7 +394,8 @@ All fields are optional. Passing an empty object (or no config at all) produces
531
394
  | Field | Type | Default | Description |
532
395
  |---|---|---|---|
533
396
  | `namespace` | `string` | `'qapture'` | Prefix for IndexedDB (`${namespace}-db`) and localStorage keys (`${namespace}:*`). Use a unique value per project to avoid storage collisions on the same origin. |
534
- | `theme` | `Partial<QaTheme>` (**deprecated, ignored**) | | **Removed in v0.3.0.** `validateConfig` ignores this key (after pushing a warning) the widget always renders the fixed Graphite design. Delete it from your config. See [Breaking Changes](#breaking-changes-v030-graphite). |
397
+ | `shotPort` | `number` | `7017` | Where `npx qapture2 shots` is listening. Only needed if you started the helper on another port. |
398
+ | `theme` | `Partial<QaTheme>` (**deprecated, ignored**) | — | **Removed in v0.3.0.** `validateConfig` ignores this key (after pushing a warning) — the widget always renders the fixed Graphite design. Delete it from your config. |
535
399
  | `brand` | `{ label?: string }` | `{ label: 'Qapture' }` | Panel heading label. |
536
400
  | `loginField` | `{ en: string; ar?: string }` | `{ en: 'Username', ar: 'اسم المستخدم' }` | Display label for the login column in the Credentials tab. |
537
401
  | `credentials` | `QaCredential[]` | `[]` | DEV/TEST/SEED login rows shown in the Credentials tab. |
@@ -540,8 +404,8 @@ All fields are optional. Passing an empty object (or no config at all) produces
540
404
  | `rtl` | `boolean` | `false` | When `true`, the UI initialises in Arabic / RTL mode. |
541
405
  | `visible` | `boolean \| undefined` | `undefined` | `true` = always show; `false` = always hide; `undefined` = dev-only (hidden in production). |
542
406
  | `alwaysVisible` | `boolean` | `false` | When `true`, overrides `visible` and shows the panel even in production. |
543
- | `hotkey` | `string` | `'shift+alt+q'` |
544
- | `captureHotkey` | `string` | `'shift+alt+c'` — jumps straight into capture mode | Keyboard shortcut that toggles the panel open/closed. |
407
+ | `hotkey` | `string` | `'shift+alt+q'` | Keyboard shortcut that toggles the panel open/closed. |
408
+ | `captureHotkey` | `string` | `'shift+alt+c'` | Keyboard shortcut that jumps straight into capture mode. |
545
409
  | `captureContext` | `boolean` | `true` | Whether to record ambient runtime context (recent console errors/warnings, uncaught errors, failed network calls, and an environment snapshot) into each note as it's captured. Set to `false` to disable entirely. See [Runtime Context Capture](#runtime-context-capture). |
546
410
 
547
411
  ### `QaTheme` (deprecated)
@@ -806,18 +670,24 @@ The CLI scaffolds `qa.config`, the agent skill, and `AGENTS.md` into any reposit
806
670
 
807
671
  ```bash
808
672
  npx qapture2 init [target-dir] [--force]
673
+ npx qapture2 shots [--port 7017] [--allow <origin>]
809
674
  npx qapture2 version
810
675
  ```
811
676
 
812
677
  `target-dir` defaults to the current directory. `--force` overwrites existing `qa.config.*` and `qa.preamble.md` (SKILL.md is always refreshed regardless).
813
678
 
679
+ `shots` is the local screenshot helper — a loopback server that runs
680
+ `screencapture` so screenshots are real photographs and never prompt. macOS
681
+ only; see [Screenshots](#screenshots-three-engines). It is the one command
682
+ here that keeps running: leave it up while you test.
683
+
814
684
  ### What it detects and generates
815
685
 
816
686
  | Step | What happens |
817
687
  |---|---|
818
688
  | Route detection | Scans `src/`, `app/`, `pages/` for route files; generates journey lanes with placeholder `'green'` steps for you to grade |
819
689
  | Credential detection | Scans `.env.example` and seeder/seed files for test logins. **Never reads `.env`, `.env.local`, `.env.production`, or any real secrets file** — enforced by a hard blocklist |
820
- | `qa.config.js` / `.ts` | Generated based on detections; contains TODO comments for manual grading. No `theme` block is emitted — v0.3.0 removed custom themes entirely (see [Breaking Changes](#breaking-changes-v030-graphite)) |
690
+ | `qa.config.js` / `.ts` | Generated based on detections; contains TODO comments for manual grading. No `theme` block is emitted — v0.3.0 removed custom themes entirely (see CHANGELOG) |
821
691
  | `qa.preamble.md` | Starter preamble file; fill with project context and paste into `config.preamble` |
822
692
  | `.claude/skills/qapture/SKILL.md` | Claude Code agent skill (always refreshed — this is a vendor artifact) |
823
693
  | `AGENTS.md` | Idempotent merge with sentinel guards; safe to run repeatedly |