qapture2 0.3.0 → 0.3.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.
@@ -264,6 +264,31 @@ function coerceJourney(raw, warnings) {
264
264
  }
265
265
  return out;
266
266
  }
267
+ function warnMissingArabic(loginField, credentials, journey, warnings) {
268
+ const hasAr = (v) => typeof v === "object" && v !== null && isNonEmptyString(v.ar);
269
+ const usesArabic = isNonEmptyString(loginField.ar) || credentials.some((c) => isNonEmptyString(c.roleAr) || hasAr(c.hint)) || journey.some((lane) => hasAr(lane.role) || lane.steps.some((s) => hasAr(s.what) || hasAr(s.expect)));
270
+ if (!usesArabic) return;
271
+ const missing = [];
272
+ for (const lane of journey) {
273
+ for (const step of lane.steps) {
274
+ if (!hasAr(step.what)) missing.push(`journey "${lane.id}" \u2192 ${step.path} (what)`);
275
+ if (step.expect !== void 0 && !hasAr(step.expect)) {
276
+ missing.push(`journey "${lane.id}" \u2192 ${step.path} (expect)`);
277
+ }
278
+ }
279
+ }
280
+ for (const c of credentials) {
281
+ if (!isNonEmptyString(c.roleAr)) missing.push(`credentials role="${c.role}" (roleAr)`);
282
+ if (c.hint !== void 0 && !hasAr(c.hint)) missing.push(`credentials role="${c.role}" (hint.ar)`);
283
+ }
284
+ if (!missing.length) return;
285
+ const LIMIT = 6;
286
+ const shown = missing.slice(0, LIMIT).join("; ");
287
+ const rest = missing.length > LIMIT ? ` (+${missing.length - LIMIT} more)` : "";
288
+ warnings.push(
289
+ `Arabic is used elsewhere in this config, but ${missing.length} bilingual field(s) have no "ar" and will show English to an Arabic-language tester: ${shown}${rest}. Every {en, ar} pair needs BOTH filled in \u2014 a plain string or an {en}-only object reads identically to a bilingual field that was simply never translated.`
290
+ );
291
+ }
267
292
  function coercePreamble(raw) {
268
293
  if (raw === null || raw === void 0) return null;
269
294
  if (typeof raw !== "object" || Array.isArray(raw)) return null;
@@ -348,6 +373,7 @@ function validateConfig(input) {
348
373
  warnings.push("visible: expected boolean \u2014 using default (dev-only)");
349
374
  }
350
375
  }
376
+ warnMissingArabic(loginField, credentials, journey, warnings);
351
377
  return {
352
378
  config: {
353
379
  namespace,
@@ -4961,16 +4987,20 @@ function CaptureMode() {
4961
4987
  };
4962
4988
  const popStyleFor = useCallback((r) => {
4963
4989
  if (typeof window === "undefined") return {};
4964
- const below = r.top + r.height + 12;
4965
- const placeAbove = below + 220 > window.innerHeight;
4966
- const top = placeAbove ? Math.max(12, r.top - 12) : below;
4990
+ const margin = 12;
4991
+ const spaceBelow = window.innerHeight - (r.top + r.height + margin);
4992
+ const spaceAbove = r.top - margin;
4993
+ const placeAbove = spaceBelow < spaceAbove;
4994
+ const top = placeAbove ? Math.max(margin, r.top - margin) : r.top + r.height + margin;
4967
4995
  let left = r.left;
4968
4996
  left = Math.min(left, window.innerWidth - 340);
4969
- left = Math.max(12, left);
4997
+ left = Math.max(margin, left);
4998
+ const available = (placeAbove ? spaceAbove : spaceBelow) - margin;
4970
4999
  return {
4971
5000
  top,
4972
5001
  left,
4973
- transform: placeAbove ? "translateY(-100%)" : "none"
5002
+ transform: placeAbove ? "translateY(-100%)" : "none",
5003
+ maxHeight: `max(${margin * 4}px, ${Math.max(0, available)}px)`
4974
5004
  };
4975
5005
  }, []);
4976
5006
  const popStyle = selection ? popStyleFor(selection.rect) : {};
@@ -5208,6 +5238,15 @@ function CaptureMode() {
5208
5238
  className: `qa-fixed qa-z-10096 qa-w-320 qa-overflow-hidden qa-rounded-xl qa-border qa-border-subtle qa-elev-3 qa-card-anim${cardIn ? " qa-card-in" : ""}`,
5209
5239
  style: {
5210
5240
  ...popStyle,
5241
+ // popStyle's maxHeight caps this to whatever room is actually
5242
+ // available above/below the target; overflowY lets the card
5243
+ // itself scroll internally rather than ever rendering content
5244
+ // (most importantly the Save button) somewhere the page's own
5245
+ // scroll — locked during capture — can't reach. overflowX stays
5246
+ // hidden so the qa-overflow-hidden class's rounded-corner
5247
+ // clipping is preserved on that axis.
5248
+ overflowY: "auto",
5249
+ overflowX: "hidden",
5211
5250
  background: "var(--qa-surface-1)"
5212
5251
  },
5213
5252
  children: [
@@ -5484,5 +5523,5 @@ function Qapture({ config }) {
5484
5523
  }
5485
5524
 
5486
5525
  export { Qapture, deleteQaDatabase, initQaStudio };
5487
- //# sourceMappingURL=chunk-ESRQOU32.js.map
5488
- //# sourceMappingURL=chunk-ESRQOU32.js.map
5526
+ //# sourceMappingURL=chunk-2CAMCMJP.js.map
5527
+ //# sourceMappingURL=chunk-2CAMCMJP.js.map
@@ -271,6 +271,31 @@ function coerceJourney(raw, warnings) {
271
271
  }
272
272
  return out;
273
273
  }
274
+ function warnMissingArabic(loginField, credentials, journey, warnings) {
275
+ const hasAr = (v) => typeof v === "object" && v !== null && isNonEmptyString(v.ar);
276
+ const usesArabic = isNonEmptyString(loginField.ar) || credentials.some((c) => isNonEmptyString(c.roleAr) || hasAr(c.hint)) || journey.some((lane) => hasAr(lane.role) || lane.steps.some((s) => hasAr(s.what) || hasAr(s.expect)));
277
+ if (!usesArabic) return;
278
+ const missing = [];
279
+ for (const lane of journey) {
280
+ for (const step of lane.steps) {
281
+ if (!hasAr(step.what)) missing.push(`journey "${lane.id}" \u2192 ${step.path} (what)`);
282
+ if (step.expect !== void 0 && !hasAr(step.expect)) {
283
+ missing.push(`journey "${lane.id}" \u2192 ${step.path} (expect)`);
284
+ }
285
+ }
286
+ }
287
+ for (const c of credentials) {
288
+ if (!isNonEmptyString(c.roleAr)) missing.push(`credentials role="${c.role}" (roleAr)`);
289
+ if (c.hint !== void 0 && !hasAr(c.hint)) missing.push(`credentials role="${c.role}" (hint.ar)`);
290
+ }
291
+ if (!missing.length) return;
292
+ const LIMIT = 6;
293
+ const shown = missing.slice(0, LIMIT).join("; ");
294
+ const rest = missing.length > LIMIT ? ` (+${missing.length - LIMIT} more)` : "";
295
+ warnings.push(
296
+ `Arabic is used elsewhere in this config, but ${missing.length} bilingual field(s) have no "ar" and will show English to an Arabic-language tester: ${shown}${rest}. Every {en, ar} pair needs BOTH filled in \u2014 a plain string or an {en}-only object reads identically to a bilingual field that was simply never translated.`
297
+ );
298
+ }
274
299
  function coercePreamble(raw) {
275
300
  if (raw === null || raw === void 0) return null;
276
301
  if (typeof raw !== "object" || Array.isArray(raw)) return null;
@@ -355,6 +380,7 @@ function validateConfig(input) {
355
380
  warnings.push("visible: expected boolean \u2014 using default (dev-only)");
356
381
  }
357
382
  }
383
+ warnMissingArabic(loginField, credentials, journey, warnings);
358
384
  return {
359
385
  config: {
360
386
  namespace,
@@ -4968,16 +4994,20 @@ function CaptureMode() {
4968
4994
  };
4969
4995
  const popStyleFor = React.useCallback((r) => {
4970
4996
  if (typeof window === "undefined") return {};
4971
- const below = r.top + r.height + 12;
4972
- const placeAbove = below + 220 > window.innerHeight;
4973
- const top = placeAbove ? Math.max(12, r.top - 12) : below;
4997
+ const margin = 12;
4998
+ const spaceBelow = window.innerHeight - (r.top + r.height + margin);
4999
+ const spaceAbove = r.top - margin;
5000
+ const placeAbove = spaceBelow < spaceAbove;
5001
+ const top = placeAbove ? Math.max(margin, r.top - margin) : r.top + r.height + margin;
4974
5002
  let left = r.left;
4975
5003
  left = Math.min(left, window.innerWidth - 340);
4976
- left = Math.max(12, left);
5004
+ left = Math.max(margin, left);
5005
+ const available = (placeAbove ? spaceAbove : spaceBelow) - margin;
4977
5006
  return {
4978
5007
  top,
4979
5008
  left,
4980
- transform: placeAbove ? "translateY(-100%)" : "none"
5009
+ transform: placeAbove ? "translateY(-100%)" : "none",
5010
+ maxHeight: `max(${margin * 4}px, ${Math.max(0, available)}px)`
4981
5011
  };
4982
5012
  }, []);
4983
5013
  const popStyle = selection ? popStyleFor(selection.rect) : {};
@@ -5215,6 +5245,15 @@ function CaptureMode() {
5215
5245
  className: `qa-fixed qa-z-10096 qa-w-320 qa-overflow-hidden qa-rounded-xl qa-border qa-border-subtle qa-elev-3 qa-card-anim${cardIn ? " qa-card-in" : ""}`,
5216
5246
  style: {
5217
5247
  ...popStyle,
5248
+ // popStyle's maxHeight caps this to whatever room is actually
5249
+ // available above/below the target; overflowY lets the card
5250
+ // itself scroll internally rather than ever rendering content
5251
+ // (most importantly the Save button) somewhere the page's own
5252
+ // scroll — locked during capture — can't reach. overflowX stays
5253
+ // hidden so the qa-overflow-hidden class's rounded-corner
5254
+ // clipping is preserved on that axis.
5255
+ overflowY: "auto",
5256
+ overflowX: "hidden",
5218
5257
  background: "var(--qa-surface-1)"
5219
5258
  },
5220
5259
  children: [
@@ -5493,5 +5532,5 @@ function Qapture({ config }) {
5493
5532
  exports.Qapture = Qapture;
5494
5533
  exports.deleteQaDatabase = deleteQaDatabase;
5495
5534
  exports.initQaStudio = initQaStudio;
5496
- //# sourceMappingURL=chunk-6KAXMN77.cjs.map
5497
- //# sourceMappingURL=chunk-6KAXMN77.cjs.map
5535
+ //# sourceMappingURL=chunk-PIN23NW3.cjs.map
5536
+ //# sourceMappingURL=chunk-PIN23NW3.cjs.map
package/dist/index.cjs CHANGED
@@ -1,24 +1,24 @@
1
1
  'use strict';
2
2
 
3
- var chunk6KAXMN77_cjs = require('./chunk-6KAXMN77.cjs');
3
+ var chunkPIN23NW3_cjs = require('./chunk-PIN23NW3.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "QaStudio", {
8
8
  enumerable: true,
9
- get: function () { return chunk6KAXMN77_cjs.Qapture; }
9
+ get: function () { return chunkPIN23NW3_cjs.Qapture; }
10
10
  });
11
11
  Object.defineProperty(exports, "Qapture", {
12
12
  enumerable: true,
13
- get: function () { return chunk6KAXMN77_cjs.Qapture; }
13
+ get: function () { return chunkPIN23NW3_cjs.Qapture; }
14
14
  });
15
15
  Object.defineProperty(exports, "deleteQaDatabase", {
16
16
  enumerable: true,
17
- get: function () { return chunk6KAXMN77_cjs.deleteQaDatabase; }
17
+ get: function () { return chunkPIN23NW3_cjs.deleteQaDatabase; }
18
18
  });
19
19
  Object.defineProperty(exports, "initQaStudio", {
20
20
  enumerable: true,
21
- get: function () { return chunk6KAXMN77_cjs.initQaStudio; }
21
+ get: function () { return chunkPIN23NW3_cjs.initQaStudio; }
22
22
  });
23
23
  //# sourceMappingURL=index.cjs.map
24
24
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Qapture as QaStudio, Qapture, deleteQaDatabase, initQaStudio } from './chunk-ESRQOU32.js';
1
+ export { Qapture as QaStudio, Qapture, deleteQaDatabase, initQaStudio } from './chunk-2CAMCMJP.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/next.cjs CHANGED
@@ -1,21 +1,21 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var chunk6KAXMN77_cjs = require('./chunk-6KAXMN77.cjs');
4
+ var chunkPIN23NW3_cjs = require('./chunk-PIN23NW3.cjs');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "QaStudio", {
9
9
  enumerable: true,
10
- get: function () { return chunk6KAXMN77_cjs.Qapture; }
10
+ get: function () { return chunkPIN23NW3_cjs.Qapture; }
11
11
  });
12
12
  Object.defineProperty(exports, "Qapture", {
13
13
  enumerable: true,
14
- get: function () { return chunk6KAXMN77_cjs.Qapture; }
14
+ get: function () { return chunkPIN23NW3_cjs.Qapture; }
15
15
  });
16
16
  Object.defineProperty(exports, "initQaStudio", {
17
17
  enumerable: true,
18
- get: function () { return chunk6KAXMN77_cjs.initQaStudio; }
18
+ get: function () { return chunkPIN23NW3_cjs.initQaStudio; }
19
19
  });
20
20
  //# sourceMappingURL=next.cjs.map
21
21
  //# sourceMappingURL=next.cjs.map
package/dist/next.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- export { Qapture as QaStudio, Qapture, initQaStudio } from './chunk-ESRQOU32.js';
2
+ export { Qapture as QaStudio, Qapture, initQaStudio } from './chunk-2CAMCMJP.js';
3
3
  //# sourceMappingURL=next.js.map
4
4
  //# sourceMappingURL=next.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk6KAXMN77_cjs = require('./chunk-6KAXMN77.cjs');
3
+ var chunkPIN23NW3_cjs = require('./chunk-PIN23NW3.cjs');
4
4
 
5
5
  // src/standalone.ts
6
6
  if (typeof window !== "undefined" && typeof customElements !== "undefined" && !customElements.get("qapture-widget")) {
@@ -38,7 +38,7 @@ if (typeof window !== "undefined" && typeof customElements !== "undefined" && !c
38
38
 
39
39
  Object.defineProperty(exports, "initQaStudio", {
40
40
  enumerable: true,
41
- get: function () { return chunk6KAXMN77_cjs.initQaStudio; }
41
+ get: function () { return chunkPIN23NW3_cjs.initQaStudio; }
42
42
  });
43
43
  //# sourceMappingURL=standalone.cjs.map
44
44
  //# sourceMappingURL=standalone.cjs.map
@@ -1,4 +1,4 @@
1
- export { initQaStudio } from './chunk-ESRQOU32.js';
1
+ export { initQaStudio } from './chunk-2CAMCMJP.js';
2
2
 
3
3
  // src/standalone.ts
4
4
  if (typeof window !== "undefined" && typeof customElements !== "undefined" && !customElements.get("qapture-widget")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qapture2",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Drop-in, AI-aware in-browser QA capture widget. A tester annotates the live app (element/region + auto-screenshot + note), tracks a graded testing journey, and exports a ZIP your own coding agent reads. Ships zero AI.",
5
5
  "type": "module",
6
6
  "license": "MIT",