@lightworkai.official/debug-capture 0.19.8 → 0.19.10

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/src/submit.ts CHANGED
@@ -110,7 +110,7 @@ export async function submitBundle(title: string, bundle: DebugBundle): Promise<
110
110
  let replay: ReplaySummary | undefined;
111
111
  const recording = replayOptions();
112
112
  if (recording) {
113
- const packed = await packReplay(trimmed.replay, recording.maxBytes);
113
+ const packed = await packReplay(trimmed.replay, recording.maxBytes, { quality: recording.canvas?.quality });
114
114
  replay = packed.summary;
115
115
  if ("dataUri" in packed) attachments.push({ dataUri: packed.dataUri, filename: packed.filename });
116
116
  }
package/src/types.ts CHANGED
@@ -188,7 +188,7 @@ export interface ReplayCapture {
188
188
  * attachment loads — or say why there is none.
189
189
  */
190
190
  export interface ReplaySummary {
191
- /** Whole seconds covered, when one was attached. */
191
+ /** Whole seconds covered by what was attached (or, with `omitted`, by what was recorded). */
192
192
  seconds?: number;
193
193
  events?: number;
194
194
  startedAt?: string;
@@ -196,8 +196,20 @@ export interface ReplaySummary {
196
196
  /** Size of the attachment as sent, and whether it is gzip. */
197
197
  bytes?: number;
198
198
  compressed?: boolean;
199
- /** The canvas frames were left out to fit under the size cap. */
199
+ /*
200
+ * What was given up to fit under the size cap (replayPack), each present
201
+ * only when it happened.
202
+ */
203
+ /** Canvas pictures left out: the canvases play blank. */
200
204
  canvasDropped?: boolean;
205
+ /** Large pictures embedded in the page (a `data:` image, an inlined font) left out. */
206
+ picturesDropped?: boolean;
207
+ /** Long text and styles shortened — only when not even one picture of the page fitted otherwise. */
208
+ simplified?: boolean;
209
+ /** Only part of the recording went: how long the whole was. `seconds` is what was sent. */
210
+ recordedSeconds?: number;
211
+ /** The part sent stops this many seconds before the report was made. */
212
+ endsEarlySeconds?: number;
201
213
  /** Present when the host records but this report carries no replay. */
202
214
  omitted?: "too-large" | "empty";
203
215
  }
@@ -25,15 +25,25 @@ import { strings } from "./strings";
25
25
  const HOST_TAG = "lw-support-chat";
26
26
  const FRAME = "lw-support-frame";
27
27
  const HOST = "lw-support-host";
28
+ /** The button's size until the desk says its own (ปุ่มแชท › ขนาดปุ่ม, 0.19.10). */
28
29
  const SIZE = 56;
29
30
  const GAP = 12;
31
+ /**
32
+ * The desk's size for it, px (0.19.10; the owner, 11-02: "make it smaller or bigger as a config") — the desk offers
33
+ * 44 to 72; anything else it might say is brought within reach of a finger and of the corner. Pure.
34
+ */
35
+ export function bubbleSize(v: unknown): number {
36
+ return typeof v === "number" && Number.isFinite(v) ? Math.round(Math.min(Math.max(v, 32), 96)) : SIZE;
37
+ }
38
+ /** Its size now — the desk's, once its look is read. */
39
+ let size = SIZE;
30
40
 
31
41
  const ICON = `<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>`;
32
42
 
33
43
  const CSS = `
34
44
  :host { all: initial; }
35
45
  .bubble {
36
- position: fixed; z-index: 2147483000; width: ${SIZE}px; height: ${SIZE}px; border-radius: 50%;
46
+ position: fixed; z-index: 2147483000; width: var(--lw-chat-size, ${SIZE}px); height: var(--lw-chat-size, ${SIZE}px); border-radius: 50%;
37
47
  display: grid; place-items: center; border: 0; cursor: pointer; padding: 0;
38
48
  background: var(--lw-chat-color, #00468f); color: #fff; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 10px -4px rgba(0, 0, 0, 0.2);
39
49
  transition: transform 150ms ease, background 150ms ease;
@@ -43,6 +53,7 @@ const CSS = `
43
53
  .bubble { touch-action: none; }
44
54
  .bubble.dragging { cursor: grabbing; transform: scale(1.06); transition: none; box-shadow: 0 18px 35px -8px rgba(0, 0, 0, 0.4); }
45
55
  .bubble:focus-visible { outline: 3px solid rgba(0, 70, 143, 0.35); outline-offset: 3px; }
56
+ .bubble > svg { width: calc(var(--lw-chat-size, ${SIZE}px) * 0.46); height: calc(var(--lw-chat-size, ${SIZE}px) * 0.46); }
46
57
  .face { display: none; width: 100%; height: 100%; border-radius: 50%; object-fit: cover; pointer-events: none; user-select: none; -webkit-user-drag: none; }
47
58
  .bubble.has-face, .bubble.has-face:hover { background: #fff; }
48
59
  .has-face .face { display: block; }
@@ -64,7 +75,7 @@ const CSS = `
64
75
  content: ""; position: absolute; top: 50%; right: -4px; width: 10px; height: 10px; background: #fff; border-radius: 2px;
65
76
  transform: translateY(-50%) rotate(45deg); box-shadow: 1px -1px 0 rgba(0, 0, 0, 0.04);
66
77
  }
67
- .label .text { display: block; max-width: min(240px, calc(100vw - ${SIZE + 64}px)); overflow: hidden; text-overflow: ellipsis; }
78
+ .label .text { display: block; max-width: min(240px, calc(100vw - var(--lw-chat-size, ${SIZE}px) - 64px)); overflow: hidden; text-overflow: ellipsis; }
68
79
  .at-left .label { right: auto; left: calc(100% + 12px); transform: translate(-4px, -50%); }
69
80
  .at-left .label::after { right: auto; left: -4px; }
70
81
  .new .label { color: var(--lw-chat-color, #00468f); }
@@ -72,7 +83,7 @@ const CSS = `
72
83
  @media (hover: hover) { .bubble:hover .label { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(0, -50%); } }
73
84
  .unlabelled .label, .bubble.dragging .label, .open .label { display: none; }
74
85
  .panel {
75
- position: fixed; z-index: 2147483001; width: min(380px, calc(100vw - 32px)); height: min(640px, calc(100vh - ${SIZE + GAP + 32}px));
86
+ position: fixed; z-index: 2147483001; width: min(380px, calc(100vw - 32px)); height: min(640px, calc(100vh - var(--lw-chat-size, ${SIZE}px) - ${GAP + 32}px));
76
87
  border-radius: 20px; overflow: hidden; background: #fff; border: 1px solid rgba(15, 23, 42, 0.08);
77
88
  box-shadow: 0 28px 64px -16px rgba(15, 23, 42, 0.3), 0 10px 24px -10px rgba(15, 23, 42, 0.14);
78
89
  animation: in 180ms cubic-bezier(0.16, 1, 0.3, 1);
@@ -126,12 +137,12 @@ export function supportFaceUrl(host: string): string {
126
137
  * How far the call's card and frame rise from their own 16px so they clear the bubble — its offset, its size and the
127
138
  * gap above it (0.19.1: 0.19.0 assumed the bubble at the corner, and a raised one sat under a ringing call). Pure.
128
139
  */
129
- export function chatLift(bubbleY: number): number {
130
- return bubbleY + SIZE + GAP - 16;
140
+ export function chatLift(bubbleY: number, s = SIZE): number {
141
+ return bubbleY + s + GAP - 16;
131
142
  }
132
143
  /** The panel above the bubble, never past the top of the window — its height for the bubble's offset (0.19.1). Pure. */
133
- export function panelHeight(bubbleY: number): string {
134
- return `min(640px, calc(100vh - ${bubbleY + SIZE + GAP + 16}px))`;
144
+ export function panelHeight(bubbleY: number, s = SIZE): string {
145
+ return `min(640px, calc(100vh - ${bubbleY + s + GAP + 16}px))`;
135
146
  }
136
147
 
137
148
  /*
@@ -151,26 +162,26 @@ const MARGIN = 8;
151
162
  /** The ring's card is ~180px tall at the corner: a bubble above that leaves it there. */
152
163
  const RING_ZONE = 200;
153
164
  /** Let go at (px, py), its centre: the nearer side, the height it was dropped at — inside the window. Pure. */
154
- export function settleBubble(px: number, py: number, view: { w: number; h: number }, inset: number): BubbleSpot {
165
+ export function settleBubble(px: number, py: number, view: { w: number; h: number }, inset: number, s = SIZE): BubbleSpot {
155
166
  const side = px < view.w / 2 ? "left" : "right";
156
- return { side, x: inset, y: clampBubbleY(view.h - py - SIZE / 2, view.h) };
167
+ return { side, x: inset, y: clampBubbleY(view.h - py - s / 2, view.h, s) };
157
168
  }
158
169
  /** A height that keeps the whole bubble on screen. Pure. */
159
- export function clampBubbleY(y: number, viewH: number): number {
160
- return Math.round(Math.min(Math.max(y, MARGIN), Math.max(MARGIN, viewH - SIZE - MARGIN)));
170
+ export function clampBubbleY(y: number, viewH: number, s = SIZE): number {
171
+ return Math.round(Math.min(Math.max(y, MARGIN), Math.max(MARGIN, viewH - s - MARGIN)));
161
172
  }
162
173
  /** How far the call's card rises to clear the bubble: only a bubble on its side, low enough to meet it. Pure. */
163
- export function liftFor(spot: BubbleSpot): number {
164
- return spot.side === "right" && spot.y < RING_ZONE ? chatLift(spot.y) : 0;
174
+ export function liftFor(spot: BubbleSpot, s = SIZE): number {
175
+ return spot.side === "right" && spot.y < RING_ZONE ? chatLift(spot.y, s) : 0;
165
176
  }
166
177
  /**
167
178
  * The panel by the bubble, where it was not dragged: above it when the room above is enough (or more than below),
168
179
  * else below it — its own height, never past the window. Pure.
169
180
  */
170
- export function anchorPanel(spot: BubbleSpot, viewH: number): { bottom?: number; top?: number; height: number } {
171
- const above = viewH - (spot.y + SIZE + GAP) - 16;
181
+ export function anchorPanel(spot: BubbleSpot, viewH: number, s = SIZE): { bottom?: number; top?: number; height: number } {
182
+ const above = viewH - (spot.y + s + GAP) - 16;
172
183
  const below = spot.y - GAP - 16;
173
- if (above >= 420 || above >= below) return { bottom: spot.y + SIZE + GAP, height: Math.max(240, Math.min(640, above)) };
184
+ if (above >= 420 || above >= below) return { bottom: spot.y + s + GAP, height: Math.max(240, Math.min(640, above)) };
174
185
  return { top: viewH - spot.y + GAP, height: Math.max(240, Math.min(640, below)) };
175
186
  }
176
187
  /** A dragged panel's top-left, inside the window by the margin. Pure. */
@@ -233,6 +244,8 @@ export interface DeskLook {
233
244
  avoid?: boolean;
234
245
  /** A page's own place (0.19.6): hidden there, or started in a corner at a height — the desk's rules. */
235
246
  places?: PlaceRule[];
247
+ /** ขนาดปุ่ม, px (0.19.10) — 56 when the desk does not say. */
248
+ size?: number;
236
249
  }
237
250
  let deskLook: DeskLook | null = null;
238
251
  /** The face's address for a desk's look: its own path on the support host, the shipped one, or none (the glyph). Pure. */
@@ -251,6 +264,15 @@ function wearLook(look: DeskLook | null): void {
251
264
  const color = look?.color && /^#[0-9a-f]{6}$/i.test(look.color) ? look.color : null;
252
265
  if (color) root.style.setProperty("--lw-chat-color", color);
253
266
  else root.style.removeProperty("--lw-chat-color");
267
+ // Its size (0.19.10): the button drawn so, and every place it stands, the panel and the call's card measured from it.
268
+ const s = bubbleSize(look?.size);
269
+ if (s !== size) {
270
+ size = s;
271
+ if (s === SIZE) root.style.removeProperty("--lw-chat-size");
272
+ else root.style.setProperty("--lw-chat-size", `${s}px`);
273
+ columns = null;
274
+ layout();
275
+ }
254
276
  const face = shadow?.querySelector<HTMLImageElement>(".face");
255
277
  const url = lookFaceUrl(getConfig().host, look);
256
278
  // Its rules and its switch may have changed where it stands.
@@ -369,7 +391,7 @@ function readColumn(side: Side, x: number): Rect[] {
369
391
  const k = `${side}:${x}`;
370
392
  let boxes = columns.boxes.get(k);
371
393
  if (!boxes) {
372
- const col = side === "right" ? { left: v.w - x - SIZE, right: v.w - x } : { left: x, right: x + SIZE };
394
+ const col = side === "right" ? { left: v.w - x - size, right: v.w - x } : { left: x, right: x + size };
373
395
  boxes = scanColumns([col], v, { extra: avoidSetting().extra })[0] ?? [];
374
396
  columns.boxes.set(k, boxes);
375
397
  }
@@ -452,7 +474,7 @@ const onPageSettled = (e: Event) => {
452
474
  const r = e.target instanceof Element ? e.target.getBoundingClientRect() : null;
453
475
  const v = view();
454
476
  const x = spot.x;
455
- const inColumn = !!r && r.width > 0 && (r.left < x + SIZE || r.right > v.w - x - SIZE);
477
+ const inColumn = !!r && r.width > 0 && (r.left < x + size || r.right > v.w - x - size);
456
478
  queueRescan(inColumn ? "soon" : "later");
457
479
  };
458
480
  /** What a person did, or the window itself changed: looked at at once. */
@@ -500,16 +522,16 @@ function layout(): void {
500
522
  if (!bubble || !panel) return;
501
523
  const v = view();
502
524
  const want = wanted();
503
- const wantY = clampBubbleY(want.y, v.h);
525
+ const wantY = clampBubbleY(want.y, v.h, size);
504
526
  // While the panel is open it stays where it was opened from — never moved under the person's eyes.
505
527
  const wish = `${pagePath()}|${want.side}|${want.x}|${wantY}`;
506
528
  const prev = shown && shownFor === wish ? shown : null;
507
529
  const home = { side: want.side, x: want.x, y: wantY };
508
530
  const placing = !!columns && avoidSetting().on;
509
- const free = !isOpen || !shown ? (placing ? placeSpot(home, readColumn, v, SIZE, MARGIN, Math.max(MARGIN, v.h - SIZE - MARGIN), prev) : home) : shown;
531
+ const free = !isOpen || !shown ? (placing ? placeSpot(home, readColumn, v, size, MARGIN, Math.max(MARGIN, v.h - size - MARGIN), prev) : home) : shown;
510
532
  shown = { side: free.side, x: free.x, y: free.y };
511
533
  shownFor = wish;
512
- if (placing) placedSig = JSON.stringify([blockedRanges(readColumn(home.side, home.x), home.side === "right" ? { left: v.w - home.x - SIZE, right: v.w - home.x } : { left: home.x, right: home.x + SIZE }, v.h, SIZE), shown]);
534
+ if (placing) placedSig = JSON.stringify([blockedRanges(readColumn(home.side, home.x), home.side === "right" ? { left: v.w - home.x - size, right: v.w - home.x } : { left: home.x, right: home.x + size }, v.h, size), shown]);
513
535
  const at = shown;
514
536
  const y = at.y;
515
537
  const other = at.side === "right" ? "left" : "right";
@@ -521,11 +543,11 @@ function layout(): void {
521
543
  const p = clampPanel(panelAt, box, v);
522
544
  Object.assign(panel.style, { left: `${p.x}px`, top: `${p.y}px`, right: "", bottom: "", height: `${box.h}px` });
523
545
  } else {
524
- const a = anchorPanel(at, v.h);
546
+ const a = anchorPanel(at, v.h, size);
525
547
  Object.assign(panel.style, { [at.side]: `${at.x}px`, [other]: "", bottom: a.bottom !== undefined ? `${a.bottom}px` : "", top: a.top !== undefined ? `${a.top}px` : "", height: `${a.height}px` });
526
548
  }
527
549
  // The call's card and frame sit above the bubble while it is there and low on their side (ring.ts, callFrame.ts).
528
- const lift = root && root.style.display !== "none" && bubble.style.display !== "none" ? liftFor(at) : 0;
550
+ const lift = root && root.style.display !== "none" && bubble.style.display !== "none" ? liftFor(at, size) : 0;
529
551
  // Written only when it differs from what <html> carries (read back, so a host that rewrites its style attribute
530
552
  // gets it again): a property there restyles the host's whole page, and layout() runs on every read of the page.
531
553
  const liftNow = lift ? `${lift}px` : "";
@@ -542,6 +564,14 @@ let pressed: { id: number; sx: number; sy: number; moved: boolean } | null = nul
542
564
  let swallowClick = false;
543
565
  function onBubbleDown(e: PointerEvent): void {
544
566
  if (e.button !== 0) return;
567
+ /*
568
+ * The press is the bubble's alone (0.19.10; the owner, 11-02: "it can move only top/bottom on the right side … it
569
+ * cannot [move to] the left"): the bubble follows the pointer at once, and the browser then looks for what to drag at
570
+ * the point first pressed — the host page's own link or picture under it there (a left menu's link) — and starts
571
+ * dragging THAT, cancelling ours (pointercancel): the bubble went back where it was. Taken here, no native drag or
572
+ * text selection begins; the press is still a click, and the panel is still focused when it opens.
573
+ */
574
+ e.preventDefault();
545
575
  pressed = { id: e.pointerId, sx: e.clientX, sy: e.clientY, moved: false };
546
576
  try {
547
577
  bubble?.setPointerCapture(e.pointerId);
@@ -554,7 +584,7 @@ function onBubbleMove(e: PointerEvent): void {
554
584
  if (!pressed.moved && Math.hypot(e.clientX - pressed.sx, e.clientY - pressed.sy) < 6) return;
555
585
  pressed.moved = true;
556
586
  bubble.classList.add("dragging");
557
- Object.assign(bubble.style, { left: `${e.clientX - SIZE / 2}px`, top: `${e.clientY - SIZE / 2}px`, right: "", bottom: "" });
587
+ Object.assign(bubble.style, { left: `${e.clientX - size / 2}px`, top: `${e.clientY - size / 2}px`, right: "", bottom: "" });
558
588
  }
559
589
  function onBubbleUp(e: PointerEvent): void {
560
590
  if (!pressed || e.pointerId !== pressed.id) return;
@@ -566,7 +596,7 @@ function onBubbleUp(e: PointerEvent): void {
566
596
  swallowClick = true;
567
597
  setTimeout(() => (swallowClick = false), 0);
568
598
  if (e.type === "pointerup") {
569
- spot = settleBubble(e.clientX, e.clientY, view(), chatPlace(getConfig().chat)?.x ?? 16);
599
+ spot = settleBubble(e.clientX, e.clientY, view(), chatPlace(getConfig().chat)?.x ?? 16, size);
570
600
  movedByHand = true;
571
601
  // Moved by hand: the panel opens by it again.
572
602
  panelAt = null;
@@ -772,6 +802,7 @@ export function refreshSupportChat(): Promise<void> {
772
802
 
773
803
  export function unmountSupportChat(): void {
774
804
  deskLook = null;
805
+ size = SIZE;
775
806
  stopFrames?.();
776
807
  stopFrames = null;
777
808
  window.removeEventListener("message", onMessage);