uidex 0.1.1 → 0.2.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.
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/core/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ IngestCapture: () => IngestCapture,
23
24
  Inspector: () => Inspector,
24
25
  Menu: () => Menu,
25
26
  Modal: () => Modal,
@@ -27,6 +28,7 @@ __export(index_exports, {
27
28
  UidexUI: () => UidexUI,
28
29
  classNames: () => classNames,
29
30
  createUidexUI: () => createUidexUI,
31
+ generateSessionId: () => generateSessionId,
30
32
  getComponents: () => getComponents,
31
33
  getContrastColor: () => getContrastColor,
32
34
  getFeatures: () => getFeatures,
@@ -36,7 +38,8 @@ __export(index_exports, {
36
38
  registerComponents: () => registerComponents,
37
39
  registerFeatures: () => registerFeatures,
38
40
  registerPages: () => registerPages,
39
- resolveColor: () => resolveColor
41
+ resolveColor: () => resolveColor,
42
+ submitFeedback: () => submitFeedback
40
43
  });
41
44
  module.exports = __toCommonJS(index_exports);
42
45
 
@@ -565,6 +568,154 @@ var Inspector = class {
565
568
  }
566
569
  };
567
570
 
571
+ // src/core/ingest.ts
572
+ var MAX_CONSOLE_LOGS = 50;
573
+ var MAX_NETWORK_ERRORS = 20;
574
+ var nativeFetch = null;
575
+ function safeStringify(value) {
576
+ if (typeof value === "string") return value;
577
+ try {
578
+ return JSON.stringify(value);
579
+ } catch {
580
+ return String(value);
581
+ }
582
+ }
583
+ var IngestCapture = class {
584
+ constructor(captureConsole, captureNetwork) {
585
+ this.captureConsole = captureConsole;
586
+ this.captureNetwork = captureNetwork;
587
+ }
588
+ consoleLogs = [];
589
+ networkErrors = [];
590
+ originalConsoleWarn = null;
591
+ originalConsoleError = null;
592
+ originalFetch = null;
593
+ start() {
594
+ this.consoleLogs = [];
595
+ this.networkErrors = [];
596
+ if (this.captureConsole) this.interceptConsole();
597
+ if (this.captureNetwork) this.interceptNetwork();
598
+ }
599
+ stop() {
600
+ this.restoreConsole();
601
+ this.restoreNetwork();
602
+ }
603
+ getConsoleLogs() {
604
+ return [...this.consoleLogs];
605
+ }
606
+ getNetworkErrors() {
607
+ return [...this.networkErrors];
608
+ }
609
+ interceptConsole() {
610
+ if (this.originalConsoleWarn) return;
611
+ this.originalConsoleWarn = console.warn;
612
+ this.originalConsoleError = console.error;
613
+ console.warn = (...args) => {
614
+ this.addConsoleLog("warn", args);
615
+ this.originalConsoleWarn.apply(console, args);
616
+ };
617
+ console.error = (...args) => {
618
+ this.addConsoleLog("error", args);
619
+ this.originalConsoleError.apply(console, args);
620
+ };
621
+ }
622
+ addConsoleLog(level, args) {
623
+ this.consoleLogs.push({
624
+ level,
625
+ message: args.map(safeStringify).join(" "),
626
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
627
+ });
628
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
629
+ this.consoleLogs.shift();
630
+ }
631
+ }
632
+ restoreConsole() {
633
+ if (this.originalConsoleWarn) {
634
+ console.warn = this.originalConsoleWarn;
635
+ this.originalConsoleWarn = null;
636
+ }
637
+ if (this.originalConsoleError) {
638
+ console.error = this.originalConsoleError;
639
+ this.originalConsoleError = null;
640
+ }
641
+ }
642
+ interceptNetwork() {
643
+ if (this.originalFetch) return;
644
+ this.originalFetch = window.fetch;
645
+ if (!nativeFetch) nativeFetch = this.originalFetch;
646
+ window.fetch = async (...args) => {
647
+ try {
648
+ const response = await this.originalFetch.apply(window, args);
649
+ if (!response.ok) {
650
+ this.addNetworkError(args[0], args[1]?.method, response.status, response.statusText);
651
+ }
652
+ return response;
653
+ } catch (error) {
654
+ this.addNetworkError(
655
+ args[0],
656
+ args[1]?.method,
657
+ null,
658
+ error instanceof Error ? error.message : "Network error"
659
+ );
660
+ throw error;
661
+ }
662
+ };
663
+ }
664
+ addNetworkError(input, method, status, statusText) {
665
+ let url;
666
+ if (typeof input === "string") {
667
+ url = input;
668
+ } else if (input instanceof URL) {
669
+ url = input.href;
670
+ } else {
671
+ url = input.url;
672
+ method ??= input.method;
673
+ }
674
+ this.networkErrors.push({
675
+ url,
676
+ method: method ?? "GET",
677
+ status,
678
+ statusText,
679
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
680
+ });
681
+ if (this.networkErrors.length > MAX_NETWORK_ERRORS) {
682
+ this.networkErrors.shift();
683
+ }
684
+ }
685
+ restoreNetwork() {
686
+ if (this.originalFetch) {
687
+ window.fetch = this.originalFetch;
688
+ this.originalFetch = null;
689
+ }
690
+ }
691
+ };
692
+ function generateSessionId() {
693
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
694
+ return crypto.randomUUID();
695
+ }
696
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
697
+ const r = Math.random() * 16 | 0;
698
+ const v = c === "x" ? r : r & 3 | 8;
699
+ return v.toString(16);
700
+ });
701
+ }
702
+ async function submitFeedback(endpoint, apiKey, report) {
703
+ const fetchFn = nativeFetch ?? fetch;
704
+ const response = await fetchFn(endpoint, {
705
+ method: "POST",
706
+ headers: {
707
+ "Content-Type": "application/json",
708
+ Authorization: `Bearer ${apiKey}`
709
+ },
710
+ body: JSON.stringify(report)
711
+ });
712
+ if (!response.ok) {
713
+ const text = await response.text().catch(() => "");
714
+ throw new Error(`Ingest failed (${response.status}): ${text}`);
715
+ }
716
+ return response.json();
717
+ }
718
+
568
719
  // src/core/modal.ts
569
720
  var Modal = class _Modal {
570
721
  backdrop = null;
@@ -1547,6 +1698,14 @@ var Modal = class _Modal {
1547
1698
  "medium"
1548
1699
  );
1549
1700
  form.appendChild(severitySelect.group);
1701
+ const titleGroup = this.createFormGroup("Title");
1702
+ const titleInput = document.createElement("input");
1703
+ titleInput.type = "text";
1704
+ titleInput.className = "uidex-form-input";
1705
+ titleInput.placeholder = "Brief summary (optional)";
1706
+ titleInput.maxLength = 200;
1707
+ titleGroup.appendChild(titleInput);
1708
+ form.appendChild(titleGroup);
1550
1709
  const descGroup = this.createFormGroup("Description");
1551
1710
  const textarea = document.createElement("textarea");
1552
1711
  textarea.className = "uidex-form-textarea";
@@ -1554,6 +1713,22 @@ var Modal = class _Modal {
1554
1713
  textarea.rows = 4;
1555
1714
  descGroup.appendChild(textarea);
1556
1715
  form.appendChild(descGroup);
1716
+ let emailInput;
1717
+ let nameInput;
1718
+ if (this.options.ingest && !this.options.ingest.reporter) {
1719
+ const reporterGroup = this.createFormGroup("Reporter");
1720
+ nameInput = document.createElement("input");
1721
+ nameInput.type = "text";
1722
+ nameInput.className = "uidex-form-input";
1723
+ nameInput.placeholder = "Name (optional)";
1724
+ reporterGroup.appendChild(nameInput);
1725
+ emailInput = document.createElement("input");
1726
+ emailInput.type = "email";
1727
+ emailInput.className = "uidex-form-input";
1728
+ emailInput.placeholder = "Email (optional)";
1729
+ reporterGroup.appendChild(emailInput);
1730
+ form.appendChild(reporterGroup);
1731
+ }
1557
1732
  const screenshotGroup = document.createElement("div");
1558
1733
  screenshotGroup.className = "uidex-form-group";
1559
1734
  const screenshotLabel = document.createElement("label");
@@ -1589,9 +1764,14 @@ var Modal = class _Modal {
1589
1764
  }
1590
1765
  const env = this.collectEnv();
1591
1766
  const page = this.data.pages.find((p) => p.componentIds.includes(id));
1767
+ const { ingest } = this.options;
1768
+ const reporterEmail = ingest?.reporter?.email || emailInput?.value.trim() || void 0;
1769
+ const reporterName = ingest?.reporter?.name || nameInput?.value.trim() || void 0;
1770
+ const titleValue = titleInput.value.trim();
1592
1771
  const report = {
1593
1772
  type: typeSelect.select.value,
1594
1773
  severity: severitySelect.select.value,
1774
+ ...titleValue ? { title: titleValue } : {},
1595
1775
  description: textarea.value.trim(),
1596
1776
  componentId: id,
1597
1777
  element: element ? this.describeElement(element) : null,
@@ -1600,17 +1780,66 @@ var Modal = class _Modal {
1600
1780
  path: window.location.pathname,
1601
1781
  route: page?.dir ?? null,
1602
1782
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1783
+ pageTitle: document.title,
1784
+ locale: navigator.language,
1785
+ sessionId: this.options.sessionId ?? "",
1603
1786
  viewport: env.viewport,
1604
1787
  screen: env.screen,
1605
1788
  userAgent: env.userAgent,
1606
- screenshot
1789
+ screenshot,
1790
+ ...reporterEmail ? { reporterEmail } : {},
1791
+ ...reporterName ? { reporterName } : {},
1792
+ ...ingest?.environment ? { environment: ingest.environment } : {},
1793
+ ...ingest?.appVersion ? { appVersion: ingest.appVersion } : {},
1794
+ ...ingest?.metadata ? { metadata: ingest.metadata } : {}
1795
+ };
1796
+ const consoleLogs = this.options.getConsoleLogs?.();
1797
+ if (consoleLogs && consoleLogs.length > 0) {
1798
+ report.consoleLogs = consoleLogs;
1799
+ }
1800
+ const networkErrors = this.options.getNetworkErrors?.();
1801
+ if (networkErrors && networkErrors.length > 0) {
1802
+ report.networkErrors = networkErrors;
1803
+ }
1804
+ const showSuccess = (autoClose) => {
1805
+ submitBtn.textContent = "Submitted!";
1806
+ if (autoClose) {
1807
+ setTimeout(() => this.hide(), 1500);
1808
+ } else {
1809
+ setTimeout(() => {
1810
+ submitBtn.textContent = "Submit";
1811
+ submitBtn.disabled = false;
1812
+ }, 1500);
1813
+ }
1607
1814
  };
1608
- console.log("[uidex] Feedback submitted:", report);
1609
- submitBtn.textContent = "Submitted!";
1610
- setTimeout(() => {
1611
- submitBtn.textContent = "Submit";
1612
- submitBtn.disabled = false;
1613
- }, 1500);
1815
+ if (ingest) {
1816
+ submitBtn.textContent = "Submitting\u2026";
1817
+ try {
1818
+ const serverResult = await submitFeedback(
1819
+ ingest.endpoint,
1820
+ ingest.apiKey,
1821
+ report
1822
+ );
1823
+ this.options.onSubmit?.(report, {
1824
+ ok: true,
1825
+ id: serverResult.id,
1826
+ sequenceNumber: serverResult.sequenceNumber
1827
+ });
1828
+ showSuccess(true);
1829
+ } catch (err) {
1830
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
1831
+ console.warn("[uidex] Feedback submission failed:", errorMessage);
1832
+ this.options.onSubmit?.(report, { ok: false, error: errorMessage });
1833
+ submitBtn.textContent = "Failed \u2014 retry?";
1834
+ submitBtn.disabled = false;
1835
+ }
1836
+ } else {
1837
+ if (!this.options.onSubmit) {
1838
+ console.log("[uidex] Feedback submitted:", report);
1839
+ }
1840
+ this.options.onSubmit?.(report, { ok: true, id: "", sequenceNumber: 0 });
1841
+ showSuccess(false);
1842
+ }
1614
1843
  });
1615
1844
  form.appendChild(submitBtn);
1616
1845
  this.mainContent.appendChild(form);
@@ -2730,49 +2959,56 @@ body.uidex-inspecting * {
2730
2959
  color: var(--uidex-color-text-muted);
2731
2960
  }
2732
2961
 
2733
- .uidex-form-select {
2734
- appearance: none;
2735
- padding: 6px 10px;
2962
+ .uidex-form-select,
2963
+ .uidex-form-input,
2964
+ .uidex-form-textarea {
2736
2965
  border: 1px solid var(--uidex-color-border);
2737
2966
  border-radius: 0;
2738
2967
  background: transparent;
2739
2968
  color: var(--uidex-color-text);
2740
2969
  font-size: var(--uidex-font-size-sm);
2741
2970
  font-family: var(--uidex-font-mono);
2742
- cursor: pointer;
2743
2971
  outline: none;
2972
+ }
2973
+
2974
+ .uidex-form-select:focus,
2975
+ .uidex-form-input:focus,
2976
+ .uidex-form-textarea:focus {
2977
+ border-color: rgba(255, 255, 255, 0.3);
2978
+ }
2979
+
2980
+ .uidex-form-input::placeholder,
2981
+ .uidex-form-textarea::placeholder {
2982
+ color: var(--uidex-color-text-muted);
2983
+ }
2984
+
2985
+ .uidex-form-select {
2986
+ appearance: none;
2987
+ padding: 6px 10px;
2988
+ cursor: pointer;
2744
2989
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ababab' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
2745
2990
  background-repeat: no-repeat;
2746
2991
  background-position: right 8px center;
2747
2992
  padding-right: 26px;
2748
2993
  }
2749
2994
 
2750
- .uidex-form-select:focus {
2751
- border-color: rgba(255, 255, 255, 0.3);
2995
+ .uidex-form-input {
2996
+ padding: 6px 10px;
2997
+ width: 100%;
2998
+ box-sizing: border-box;
2999
+ }
3000
+
3001
+ .uidex-form-input + .uidex-form-input {
3002
+ margin-top: 6px;
2752
3003
  }
2753
3004
 
2754
3005
  .uidex-form-textarea {
2755
3006
  padding: 8px 10px;
2756
- border: 1px solid var(--uidex-color-border);
2757
- border-radius: 0;
2758
- background: transparent;
2759
- color: var(--uidex-color-text);
2760
- font-size: var(--uidex-font-size-sm);
2761
- font-family: var(--uidex-font-mono);
2762
3007
  line-height: 1.5;
2763
3008
  resize: vertical;
2764
- outline: none;
2765
3009
  min-height: 80px;
2766
3010
  }
2767
3011
 
2768
- .uidex-form-textarea::placeholder {
2769
- color: var(--uidex-color-text-muted);
2770
- }
2771
-
2772
- .uidex-form-textarea:focus {
2773
- border-color: rgba(255, 255, 255, 0.3);
2774
- }
2775
-
2776
3012
  .uidex-form-submit {
2777
3013
  padding: 6px 16px;
2778
3014
  border: 1px solid var(--uidex-color-border);
@@ -2791,7 +3027,7 @@ body.uidex-inspecting * {
2791
3027
  background: var(--uidex-color-primary-hover);
2792
3028
  }
2793
3029
 
2794
- .uidex-form-submit:active {
3030
+ .uidex-form-submit:not(:disabled):active {
2795
3031
  transform: translateY(1px);
2796
3032
  }
2797
3033
 
@@ -2935,8 +3171,17 @@ var UidexUI = class {
2935
3171
  copyTimer = null;
2936
3172
  currentPresentIds = [];
2937
3173
  activeMode = null;
3174
+ sessionId;
3175
+ capture = null;
2938
3176
  constructor(options = {}) {
2939
3177
  this.options = options;
3178
+ this.sessionId = generateSessionId();
3179
+ if (options.ingest?.captureConsole || options.ingest?.captureNetwork) {
3180
+ this.capture = new IngestCapture(
3181
+ options.ingest.captureConsole ?? false,
3182
+ options.ingest.captureNetwork ?? false
3183
+ );
3184
+ }
2940
3185
  this.overlay = new Overlay({
2941
3186
  color: options.config?.defaults?.color,
2942
3187
  borderStyle: options.config?.defaults?.borderStyle,
@@ -2953,7 +3198,12 @@ var UidexUI = class {
2953
3198
  }
2954
3199
  this.options.onSelect?.(id);
2955
3200
  },
2956
- elementGetter: (id) => this.findElement(id)
3201
+ elementGetter: (id) => this.findElement(id),
3202
+ ingest: options.ingest,
3203
+ onSubmit: options.onSubmit,
3204
+ sessionId: this.sessionId,
3205
+ getConsoleLogs: () => this.capture?.getConsoleLogs() ?? [],
3206
+ getNetworkErrors: () => this.capture?.getNetworkErrors() ?? []
2957
3207
  });
2958
3208
  this.menu = new Menu({
2959
3209
  onInspectToggle: () => this.toggleMode("inspect"),
@@ -3004,12 +3254,14 @@ var UidexUI = class {
3004
3254
  this.modal.setShadowRoot(this.shadowRoot);
3005
3255
  this.updateModalData(presentIds);
3006
3256
  this.inspector?.mount();
3257
+ this.capture?.start();
3007
3258
  this.startObserving();
3008
3259
  this.mounted = true;
3009
3260
  }
3010
3261
  destroy() {
3011
3262
  if (!this.mounted) return;
3012
3263
  this.stopObserving();
3264
+ this.capture?.stop();
3013
3265
  if (this.copyTimer !== null) {
3014
3266
  clearTimeout(this.copyTimer);
3015
3267
  this.copyTimer = null;
@@ -3196,6 +3448,7 @@ function createUidexUI(options = {}) {
3196
3448
  }
3197
3449
  // Annotate the CommonJS export names for ESM import in node:
3198
3450
  0 && (module.exports = {
3451
+ IngestCapture,
3199
3452
  Inspector,
3200
3453
  Menu,
3201
3454
  Modal,
@@ -3203,6 +3456,7 @@ function createUidexUI(options = {}) {
3203
3456
  UidexUI,
3204
3457
  classNames,
3205
3458
  createUidexUI,
3459
+ generateSessionId,
3206
3460
  getComponents,
3207
3461
  getContrastColor,
3208
3462
  getFeatures,
@@ -3212,6 +3466,7 @@ function createUidexUI(options = {}) {
3212
3466
  registerComponents,
3213
3467
  registerFeatures,
3214
3468
  registerPages,
3215
- resolveColor
3469
+ resolveColor,
3470
+ submitFeedback
3216
3471
  });
3217
3472
  //# sourceMappingURL=index.cjs.map