@alliance-droid/status-feedback-system 2.0.0 → 2.1.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.
@@ -11,7 +11,6 @@
11
11
  } from '@alliance-droid/svelte-component-library';
12
12
  import DevContextMenu from './dev/DevContextMenu.svelte';
13
13
  import DevOnboardingToast from './dev/DevOnboardingToast.svelte';
14
- import { captureScreenshot, type ScreenshotProvider } from './dev/screenshot.js';
15
14
  import {
16
15
  detectEnvironment,
17
16
  getEnvironmentName,
@@ -40,13 +39,10 @@
40
39
  */
41
40
  userEmail: string;
42
41
  /**
43
- * Custom screenshot provider. When supplied, this is called instead of
44
- * the default html2canvas client-side capture. Use this to delegate
45
- * screenshot capture to a server endpoint (e.g. Puppeteer / screenshot-service).
46
- *
47
- * See `createServerScreenshotProvider()` for a ready-made helper.
42
+ * Endpoint on the consumer app that issues render tokens.
43
+ * Default: '/svc/internal/issue-render-token'
48
44
  */
49
- screenshotProvider?: ScreenshotProvider;
45
+ renderTokenEndpoint?: string;
50
46
  }
51
47
 
52
48
  let {
@@ -57,7 +53,7 @@
57
53
  showOnboarding = true,
58
54
  boardUrl,
59
55
  userEmail,
60
- screenshotProvider,
56
+ renderTokenEndpoint = '/svc/internal/issue-render-token',
61
57
  }: Props = $props();
62
58
 
63
59
  // Dev-mode warning when userEmail is empty
@@ -151,9 +147,9 @@
151
147
  let submitting = $state(false);
152
148
  let submitResult = $state<'success' | 'error' | null>(null);
153
149
 
154
- let screenshotData = $state<string | null>(null);
155
- let capturingScreenshot_ = $state(false);
156
- let screenshotFailed = $state(false);
150
+ let renderContext = $state<{ url: string; scrollX: number; scrollY: number; renderToken: string; issuer: string } | null>(null);
151
+ let fetchingRenderToken = $state(false);
152
+ let renderTokenFailed = $state(false);
157
153
 
158
154
  // Drag state
159
155
  let isDragging = $state(false);
@@ -249,16 +245,36 @@
249
245
  if (e.key === 'Escape') {
250
246
  menuOpen = false;
251
247
  formOpen = false;
252
- screenshotData = null;
248
+ renderContext = null;
253
249
  }
254
250
  }
255
251
 
256
- async function doScreenshot() {
257
- capturingScreenshot_ = true;
258
- screenshotFailed = false;
259
- screenshotData = await captureScreenshot(screenshotProvider);
260
- if (!screenshotData) screenshotFailed = true;
261
- capturingScreenshot_ = false;
252
+ async function fetchRenderToken() {
253
+ fetchingRenderToken = true;
254
+ renderTokenFailed = false;
255
+ try {
256
+ const url = window.location.href;
257
+ const scrollX = Math.round(window.scrollX);
258
+ const scrollY = Math.round(window.scrollY);
259
+ const res = await fetch(renderTokenEndpoint, {
260
+ method: 'POST',
261
+ credentials: 'include',
262
+ headers: { 'Content-Type': 'application/json' },
263
+ body: JSON.stringify({ url, scrollX, scrollY }),
264
+ });
265
+ if (!res.ok) {
266
+ renderTokenFailed = true;
267
+ console.warn('[DevFeedback] Render token fetch failed:', res.status);
268
+ return;
269
+ }
270
+ const { token } = (await res.json()) as { token: string };
271
+ renderContext = { url, scrollX, scrollY, renderToken: token, issuer: new URL(url).origin };
272
+ } catch (err) {
273
+ renderTokenFailed = true;
274
+ console.warn('[DevFeedback] Render token fetch error:', err);
275
+ } finally {
276
+ fetchingRenderToken = false;
277
+ }
262
278
  }
263
279
 
264
280
  function openForm(type: 'bug' | 'feature') {
@@ -273,9 +289,9 @@
273
289
  modalY = 0;
274
290
 
275
291
  if (type === 'bug') {
276
- doScreenshot();
292
+ fetchRenderToken();
277
293
  } else {
278
- screenshotData = null;
294
+ renderContext = null;
279
295
  }
280
296
  }
281
297
 
@@ -302,8 +318,11 @@
302
318
  metadata.consoleErrors = [...consoleErrors];
303
319
  }
304
320
 
305
- if (screenshotData) {
306
- metadata.screenshot = screenshotData;
321
+ if (renderContext) {
322
+ metadata.renderToken = renderContext.renderToken;
323
+ metadata.issuer = renderContext.issuer;
324
+ metadata.scrollX = renderContext.scrollX;
325
+ metadata.scrollY = renderContext.scrollY;
307
326
  }
308
327
 
309
328
  const gitCommit = getEnvVar('VERCEL_GIT_COMMIT_SHA');
@@ -326,7 +345,7 @@
326
345
  setTimeout(() => {
327
346
  formOpen = false;
328
347
  submitResult = null;
329
- screenshotData = null;
348
+ renderContext = null;
330
349
  }, 2000);
331
350
  } catch {
332
351
  submitResult = 'error';
@@ -451,40 +470,30 @@
451
470
  {/if}
452
471
  </div>
453
472
 
454
- <!-- Screenshot preview -->
455
- {#if capturingScreenshot_}
473
+ <!-- Render token status -->
474
+ {#if fetchingRenderToken}
456
475
  <div class="flex items-center gap-2 text-sm text-foreground-tertiary">
457
- <Spinner /> Capturing screenshot...
476
+ <Spinner /> Preparing screenshot...
458
477
  </div>
459
- {:else if screenshotFailed}
460
- <div class="rounded-md border border-border bg-surface p-3">
461
- <div class="flex items-center gap-2 text-sm text-foreground-secondary mb-1">
462
- <i class="fa-solid fa-image-slash" aria-hidden="true"></i>
463
- <span>Screenshot capture failed</span>
464
- </div>
465
- <p class="text-xs text-foreground-tertiary mb-2">Check console for error. May need: <code class="bg-background px-1 rounded">npm install html2canvas</code></p>
466
- <button
467
- class="text-xs text-primary hover:underline bg-transparent border-none cursor-pointer p-0"
468
- onclick={doScreenshot}
469
- >
470
- Retry
471
- </button>
472
- </div>
473
- {:else if screenshotData}
474
- <div class="relative">
475
- <img
476
- src={screenshotData}
477
- alt="Screenshot"
478
- class="w-full rounded-md border border-border max-h-40 object-cover object-top"
479
- />
478
+ {:else if renderTokenFailed}
479
+ <div class="rounded-md border border-border bg-surface p-3">
480
+ <div class="flex items-center gap-2 text-sm text-foreground-secondary mb-1">
481
+ <i class="fa-solid fa-image-slash" aria-hidden="true"></i>
482
+ <span>Screenshot setup failed</span>
483
+ </div>
484
+ <p class="text-xs text-foreground-tertiary mb-2">Your feedback will be submitted without a screenshot.</p>
480
485
  <button
481
- class="absolute top-1 right-1 bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs border-none cursor-pointer hover:bg-black/80"
482
- onclick={() => (screenshotData = null)}
483
- aria-label="Remove screenshot"
486
+ class="text-xs text-primary hover:underline bg-transparent border-none cursor-pointer p-0"
487
+ onclick={fetchRenderToken}
484
488
  >
485
- <i class="fa-solid fa-xmark"></i>
489
+ Retry
486
490
  </button>
487
491
  </div>
492
+ {:else if renderContext}
493
+ <div class="rounded-md border border-border bg-surface-secondary p-2 flex items-center gap-2 text-sm text-foreground-secondary">
494
+ <i class="fa-solid fa-camera" aria-hidden="true"></i>
495
+ <span>Screenshot will be captured server-side on submit</span>
496
+ </div>
488
497
  {/if}
489
498
  </div>
490
499
  {/if}
@@ -1,4 +1,3 @@
1
- import { type ScreenshotProvider } from './dev/screenshot.js';
2
1
  interface Props {
3
2
  apiUrl: string;
4
3
  apiKey: string;
@@ -19,13 +18,10 @@ interface Props {
19
18
  */
20
19
  userEmail: string;
21
20
  /**
22
- * Custom screenshot provider. When supplied, this is called instead of
23
- * the default html2canvas client-side capture. Use this to delegate
24
- * screenshot capture to a server endpoint (e.g. Puppeteer / screenshot-service).
25
- *
26
- * See `createServerScreenshotProvider()` for a ready-made helper.
21
+ * Endpoint on the consumer app that issues render tokens.
22
+ * Default: '/svc/internal/issue-render-token'
27
23
  */
28
- screenshotProvider?: ScreenshotProvider;
24
+ renderTokenEndpoint?: string;
29
25
  }
30
26
  declare const DevFeedback: import("svelte").Component<Props, {}, "">;
31
27
  type DevFeedback = ReturnType<typeof DevFeedback>;
package/dist/index.d.ts CHANGED
@@ -9,8 +9,6 @@
9
9
  */
10
10
  export type { Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse, FeedbackVisibility, StatusHistoryEntry, SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus, ApiResponse, } from './types.js';
11
11
  export { deriveOverallStatus } from './utils.js';
12
- export { createServerScreenshotProvider } from './components/dev/screenshot.js';
13
- export type { ScreenshotProvider } from './components/dev/screenshot.js';
14
12
  export { createStatusClient } from './api.js';
15
13
  export type { StatusClient, StatusClientConfig, StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory } from './api.js';
16
14
  export { default as StatusPage } from './components/StatusPage.svelte';
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@
9
9
  */
10
10
  // ── Utilities ──
11
11
  export { deriveOverallStatus } from './utils.js';
12
- export { createServerScreenshotProvider } from './components/dev/screenshot.js';
13
12
  // ── API Client ──
14
13
  export { createStatusClient } from './api.js';
15
14
  // ── Client Components ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alliance-droid/status-feedback-system",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Drop-in system status pages and user feedback forms for SvelteKit apps",
6
6
  "svelte": "./dist/index.js",
@@ -18,30 +18,32 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "build": "svelte-package -o dist && publint",
21
- "lint": "svelte-check --tsconfig ./tsconfig.json",
21
+ "lint": "eslint src",
22
22
  "check": "svelte-check --tsconfig ./tsconfig.json",
23
23
  "test": "vitest run",
24
24
  "test:watch": "vitest",
25
25
  "prepublishOnly": "npm run build"
26
26
  },
27
- "dependencies": {
28
- "html2canvas": "^1.4.1"
29
- },
30
27
  "peerDependencies": {
31
28
  "@alliance-droid/svelte-component-library": ">=1.0.0",
32
29
  "svelte": "^5.0.0"
33
30
  },
34
31
  "devDependencies": {
35
32
  "@alliance-droid/svelte-component-library": "^1.0.5",
33
+ "@eslint/js": "^10.0.1",
36
34
  "@sveltejs/package": "^2.3.0",
37
35
  "@sveltejs/vite-plugin-svelte": "^6.0.0",
38
36
  "@testing-library/jest-dom": "^6.9.1",
39
37
  "@testing-library/svelte": "^5.3.1",
38
+ "eslint": "^10.4.0",
39
+ "eslint-plugin-svelte": "^3.17.1",
40
+ "globals": "^17.6.0",
40
41
  "jsdom": "^29.0.0",
41
42
  "publint": "^0.3.0",
42
43
  "svelte": "^5.48.0",
43
44
  "svelte-check": "^4.3.0",
44
45
  "typescript": "^5.9.0",
46
+ "typescript-eslint": "^8.59.4",
45
47
  "vite": "^7.3.0",
46
48
  "vitest": "^4.0.0"
47
49
  },
@@ -61,4 +63,4 @@
61
63
  "type": "git",
62
64
  "url": "git+https://alliance.ghe.com/alliance/status-feedback-system.git"
63
65
  }
64
- }
66
+ }
@@ -1,36 +0,0 @@
1
- /**
2
- * Screenshot capture utility for DevFeedback.
3
- *
4
- * Supports two modes:
5
- * 1. Custom provider — consumer supplies a `ScreenshotProvider` callback
6
- * (e.g. server-side capture via Puppeteer / screenshot-service).
7
- * 2. html2canvas fallback — dynamically imports html2canvas if no provider
8
- * is given (html2canvas is an optional peer dependency).
9
- */
10
- /**
11
- * A function that captures a screenshot and returns a data URL (or null on failure).
12
- * Consumers can implement this however they like — server endpoint, different
13
- * client library, canvas API, etc.
14
- */
15
- export type ScreenshotProvider = () => Promise<string | null>;
16
- /**
17
- * Capture a screenshot using the given provider, or fall back to html2canvas.
18
- */
19
- export declare function captureScreenshot(provider?: ScreenshotProvider): Promise<string | null>;
20
- /**
21
- * Create a screenshot provider that POSTs to a server endpoint.
22
- *
23
- * The endpoint receives `{ url, viewport }` and should return
24
- * `{ screenshot: "data:image/..." }` (or any JSON with a `screenshot` string field).
25
- *
26
- * @example
27
- * ```ts
28
- * <DevFeedback
29
- * screenshotProvider={createServerScreenshotProvider('/api/screenshot')}
30
- * ...
31
- * />
32
- * ```
33
- */
34
- export declare function createServerScreenshotProvider(endpoint: string, options?: {
35
- headers?: Record<string, string>;
36
- }): ScreenshotProvider;
@@ -1,94 +0,0 @@
1
- /**
2
- * Screenshot capture utility for DevFeedback.
3
- *
4
- * Supports two modes:
5
- * 1. Custom provider — consumer supplies a `ScreenshotProvider` callback
6
- * (e.g. server-side capture via Puppeteer / screenshot-service).
7
- * 2. html2canvas fallback — dynamically imports html2canvas if no provider
8
- * is given (html2canvas is an optional peer dependency).
9
- */
10
- /**
11
- * Capture a screenshot using the given provider, or fall back to html2canvas.
12
- */
13
- export async function captureScreenshot(provider) {
14
- if (provider) {
15
- try {
16
- return await provider();
17
- }
18
- catch (err) {
19
- console.error('[DevFeedback] Screenshot provider failed:', err);
20
- return null;
21
- }
22
- }
23
- return captureWithHtml2Canvas();
24
- }
25
- /**
26
- * Create a screenshot provider that POSTs to a server endpoint.
27
- *
28
- * The endpoint receives `{ url, viewport }` and should return
29
- * `{ screenshot: "data:image/..." }` (or any JSON with a `screenshot` string field).
30
- *
31
- * @example
32
- * ```ts
33
- * <DevFeedback
34
- * screenshotProvider={createServerScreenshotProvider('/api/screenshot')}
35
- * ...
36
- * />
37
- * ```
38
- */
39
- export function createServerScreenshotProvider(endpoint, options) {
40
- return async () => {
41
- const res = await fetch(endpoint, {
42
- method: 'POST',
43
- headers: {
44
- 'Content-Type': 'application/json',
45
- ...options?.headers,
46
- },
47
- body: JSON.stringify({
48
- url: window.location.href,
49
- viewport: {
50
- width: window.innerWidth,
51
- height: window.innerHeight,
52
- },
53
- }),
54
- });
55
- if (!res.ok)
56
- return null;
57
- const data = await res.json();
58
- return data.screenshot ?? null;
59
- };
60
- }
61
- /** html2canvas fallback — gracefully degrades if not installed. */
62
- async function captureWithHtml2Canvas() {
63
- try {
64
- const html2canvas = (await import('html2canvas')).default;
65
- const canvas = await html2canvas(document.body, {
66
- logging: false,
67
- useCORS: true,
68
- scale: 1,
69
- width: window.innerWidth,
70
- height: window.innerHeight,
71
- windowWidth: window.innerWidth,
72
- windowHeight: window.innerHeight,
73
- ignoreElements: (element) => {
74
- // Skip elements that might have unsupported CSS (oklab, oklch, color-mix)
75
- const style = window.getComputedStyle(element);
76
- const hasUnsupportedColor = [
77
- style.color,
78
- style.backgroundColor,
79
- style.borderColor,
80
- ].some(color => color && (color.includes('oklab') || color.includes('oklch') || color.includes('color-mix')));
81
- return hasUnsupportedColor;
82
- },
83
- });
84
- return canvas.toDataURL('image/jpeg', 0.7);
85
- }
86
- catch (err) {
87
- console.error('[DevFeedback] Screenshot capture failed:', err);
88
- if (err instanceof Error && err.message.includes('oklab')) {
89
- console.warn('[DevFeedback] Your CSS uses oklab/oklch colors which html2canvas doesn\'t support. Screenshot skipped.');
90
- }
91
- // html2canvas failed (unsupported CSS, cross-origin iframe, etc.)
92
- return null;
93
- }
94
- }