@lightworkai.official/debug-capture 0.14.1 → 0.14.2

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/ui/element.ts CHANGED
@@ -148,18 +148,76 @@ function createReporterClass(): CustomElementConstructor {
148
148
  this.returnFocus = active instanceof HTMLElement && active !== document.body ? active : null;
149
149
  const t = strings(getConfig().locale);
150
150
  /*
151
- * One door: the press takes the screen and opens the report form,
152
- * always. Asking the team is a choice made IN that form — เป็นคำขอ?
153
- * เลือกเรื่องที่ขอ — never a door before it (docs/WIDGET-ASK-PLAN.md): a
154
- * chooser here cost every press one more click to serve the one in
155
- * twelve that asks, and a press before the offer was read had no way to
156
- * ask at all. The offer is read without waiting — the share needs this
157
- * click — and the form shows the door whenever it lands.
151
+ * The owner's order (docs/WIDGET-ASK-PLAN.md, 0.14.2): where the desk
152
+ * offers something to ask, the press asks first — แจ้งปัญหา or คำขอ —
153
+ * because a request has no use for the screen and must not be asked to
154
+ * share it. Read from what is already known, never awaited: the share
155
+ * has to start inside the press that chose แจ้งปัญหา. An offer not read
156
+ * yet is the report as it always was, and its form shows the two cards
157
+ * when the offer lands.
158
158
  */
159
- if (requestTypesNow() === null) void loadRequestTypes();
159
+ const offered = requestTypesNow();
160
+ if (offered === null) void loadRequestTypes();
161
+ if (reason.type === "manual" && offered?.length) {
162
+ this.renderChooser(t, reason);
163
+ return;
164
+ }
160
165
  await this.report(reason, t);
161
166
  }
162
167
 
168
+ /** แจ้งปัญหา or คำขอ — the two cards, before anything is asked of the browser. */
169
+ private renderChooser(t: Strings, reason: DebugReason): void {
170
+ const panel = this.shell(t);
171
+ panel.classList.add("narrow");
172
+ panel.setAttribute("aria-label", t.chooseTitle);
173
+ const head = el("div", "head");
174
+ const closeBtn = button("✕", "icon");
175
+ closeBtn.setAttribute("aria-label", t.close);
176
+ closeBtn.addEventListener("click", () => this.close());
177
+ head.append(el("h2", "", t.chooseTitle), closeBtn);
178
+
179
+ const problem = kindButton("problem", BUG, t.kindProblem, t.chooseProblemHint);
180
+ problem.dataset.choose = "problem";
181
+ // Our own dialog leaves the screen before the share starts, or it is in the picture.
182
+ problem.addEventListener("click", () => {
183
+ this.root.replaceChildren();
184
+ void this.report(reason, t);
185
+ });
186
+ const ask = kindButton("request", WORKFLOW, t.kindRequest, t.chooseRequestHint);
187
+ ask.dataset.choose = "request";
188
+ // A request: no share asked for. The page's address still travels with it.
189
+ ask.addEventListener("click", () => {
190
+ this.bundle = buildBundle({ reason, note: "", screenshotDataUrl: null, screenshotMethod: "none", erroredQueries: safeQueries() });
191
+ this.renderForm(t, reason, "request");
192
+ });
193
+ const choices = el("div", "kinds choose");
194
+ choices.append(problem, ask);
195
+ choices.addEventListener("keydown", (e) => {
196
+ if (!ARROWS.includes(e.key)) return;
197
+ e.preventDefault();
198
+ (this.root.activeElement === problem ? ask : problem).focus();
199
+ });
200
+ panel.append(head, choices);
201
+ problem.focus();
202
+ }
203
+
204
+ /**
205
+ * คำขอ → แจ้งปัญหา on a request that began with no screen: the share is
206
+ * asked for now, inside this press, with our dialog off the screen. What
207
+ * was typed crosses over; a share refused goes back to the request, as it
208
+ * was.
209
+ */
210
+ private async switchToReport(t: Strings, reason: DebugReason, carried: Draft): Promise<void> {
211
+ this.root.replaceChildren();
212
+ const shot = await captureScreen();
213
+ if (shot.method === "cancelled") {
214
+ this.renderForm(t, reason, "request", { ...carried, asking: true });
215
+ return;
216
+ }
217
+ this.bundle = buildBundle({ reason, note: "", screenshotDataUrl: shot.dataUrl, screenshotMethod: shot.method, erroredQueries: safeQueries() });
218
+ this.renderForm(t, reason, "problem", carried);
219
+ }
220
+
163
221
  /** A report: the screen first, then the form — see open(). Called inside a click, with nothing awaited before the capture. */
164
222
  private async report(reason: DebugReason, t: Strings): Promise<void> {
165
223
  /*
@@ -186,7 +244,7 @@ function createReporterClass(): CustomElementConstructor {
186
244
  screenshotMethod: shot.method,
187
245
  erroredQueries: safeQueries(),
188
246
  });
189
- this.renderForm(t, reason);
247
+ this.renderForm(t, reason, "problem");
190
248
  }
191
249
 
192
250
  /** Closed unsent, what was typed is kept for the next press; sent, it is gone. */
@@ -236,11 +294,13 @@ function createReporterClass(): CustomElementConstructor {
236
294
  * leaves the page's logs behind — they are a problem's evidence, not an
237
295
  * ask's — and its picture can be taken off.
238
296
  */
239
- private renderForm(t: Strings, reason: DebugReason): void {
297
+ private renderForm(t: Strings, reason: DebugReason, start: "problem" | "request", carried?: Draft): void {
240
298
  const bundle = this.bundle;
241
299
  if (!bundle) return;
242
300
  const panel = this.shell(t);
243
- let mode: "problem" | "request" = "problem";
301
+ let mode: "problem" | "request" = start;
302
+ // Was the screen asked for? A request chosen first was not — แจ้งปัญหา asks for it then.
303
+ const screenTaken = start === "problem" || !!bundle.screenshotDataUrl;
244
304
  let chosen: RequestTypeOption | null = null;
245
305
 
246
306
  const head = el("div", "head");
@@ -251,27 +311,24 @@ function createReporterClass(): CustomElementConstructor {
251
311
  head.append(heading, closeBtn);
252
312
 
253
313
  /*
254
- * แจ้งปัญหา or คำขอ — two cards under the heading, the way the desk's own
255
- * สร้างเวิร์กโฟลว์ asks it, shown only where the desk offers something to
256
- * ask. แจ้งปัญหา is chosen as the form opens (the press is a report for
257
- * most, so no click is added); either card switches the form, any time,
258
- * and nothing typed is lost by it.
314
+ * แจ้งปัญหา or คำขอ — the same two cards the press chose from, kept under
315
+ * the heading so the form can be switched any time with nothing typed
316
+ * lost (to แจ้งปัญหา from a request that began with no screen, the
317
+ * share is asked for then — switchToReport). Shown only where the desk
318
+ * offers something to ask.
259
319
  */
260
320
  const kinds = el("div", "kinds");
261
321
  kinds.hidden = true;
262
322
  kinds.setAttribute("role", "radiogroup");
263
323
  kinds.setAttribute("aria-label", t.chooseTitle);
264
324
  const kindCard = (kind: "problem" | "request", icon: IconNode, title: string, hint: string) => {
265
- const card = button("", "kind");
325
+ const card = kindButton(kind, icon, title, hint);
266
326
  card.setAttribute("role", "radio");
267
- card.dataset.kind = kind;
268
- const top = el("span", "kind-top");
269
- top.append(svgIcon(icon), el("b", "", title));
270
- card.append(top, el("span", "kind-hint", hint), svgIcon(CHECK, "tick"));
271
327
  card.addEventListener("click", () => setMode(kind));
272
328
  return card;
273
329
  };
274
- const problemCard = kindCard("problem", BUG, t.kindProblem, t.kindProblemHint);
330
+ // From a request that began with no screen, แจ้งปัญหา will ask for it — said on the card, as the chooser says it.
331
+ const problemCard = kindCard("problem", BUG, t.kindProblem, screenTaken ? t.kindProblemHint : t.chooseProblemHint);
275
332
  const requestCard = kindCard("request", WORKFLOW, t.kindRequest, t.kindRequestHint);
276
333
  kinds.append(problemCard, requestCard);
277
334
  // A radio group's own keys: an arrow moves the choice, and the focus with it.
@@ -366,16 +423,24 @@ function createReporterClass(): CustomElementConstructor {
366
423
  logs.v.textContent = asking ? t.requestLeavesWhat : captured;
367
424
  shotChoice.hidden = !asking || !bundle.screenshotDataUrl;
368
425
  if (markWhat) fillLabel(markWhat, asking ? t.requestAnnotateHint : t.annotateHint);
369
- for (const node of shot) node.classList.toggle("dropped", asking && !withShot.checked);
426
+ for (const node of shot) {
427
+ node.classList.toggle("dropped", asking && !withShot.checked);
428
+ // A request that began with no screen has no picture to speak of.
429
+ node.hidden = asking && !bundle.screenshotDataUrl;
430
+ }
370
431
  submitBtn.textContent = asking ? t.requestSubmit : t.submit;
371
432
  ready();
372
433
  };
373
434
  withShot.addEventListener("change", apply);
374
- function setMode(next: "problem" | "request"): void {
435
+ const setMode = (next: "problem" | "request"): void => {
375
436
  if (next === mode) return;
437
+ if (next === "problem" && !screenTaken) {
438
+ void this.switchToReport(t, reason, { title: summary.value, detail: detail.value, asking: false, typeId: chosen?.id ?? null });
439
+ return;
440
+ }
376
441
  mode = next;
377
442
  apply();
378
- }
443
+ };
379
444
 
380
445
  /*
381
446
  * What the desk offers, as soon as it is known: now, or when the read
@@ -422,12 +487,11 @@ function createReporterClass(): CustomElementConstructor {
422
487
  };
423
488
  /*
424
489
  * What was typed last time and not sent comes back, said so — with a
425
- * way to clear it. A request goes back to being one once its type is
426
- * offered again.
490
+ * way to clear it. Carried across a switch, the words are simply there.
427
491
  */
428
- const restored = draft && (draft.title || draft.detail) ? draft : null;
492
+ const restored = carried ?? (draft && (draft.title || draft.detail) ? draft : null);
429
493
  const again = el("p", "draft");
430
- again.hidden = !restored;
494
+ again.hidden = !restored || !!carried;
431
495
  if (restored) {
432
496
  summary.value = restored.title;
433
497
  detail.value = restored.detail;
@@ -446,14 +510,13 @@ function createReporterClass(): CustomElementConstructor {
446
510
  const known = requestTypesNow();
447
511
  const offerAndRestore = (types: RequestTypeOption[]) => {
448
512
  offer(types);
449
- if (!restored?.asking || kinds.hidden) return;
513
+ // The kind is the press's choice; a request's type comes back as it was.
514
+ if (mode !== "request" || kinds.hidden || !restored?.typeId) return;
450
515
  const was = types.find((x) => x.id === restored.typeId);
451
516
  if (was) {
452
517
  chosen = was;
453
518
  markType();
454
519
  }
455
- mode = "request";
456
- apply();
457
520
  };
458
521
  if (known) offerAndRestore(known);
459
522
  else if (reason.type === "manual") void loadRequestTypes().then(offerAndRestore);
@@ -623,6 +686,16 @@ const CLOCK: IconNode = [
623
686
  ];
624
687
  const CHECK: IconNode = [["path", { d: "M20 6 9 17l-5-5" }]];
625
688
  const SVG_NS = "http://www.w3.org/2000/svg";
689
+ /** A card for แจ้งปัญหา or คำขอ — the chooser's and the form's, one drawing. */
690
+ function kindButton(kind: "problem" | "request", icon: IconNode, title: string, hint: string): HTMLButtonElement {
691
+ const card = button("", "kind");
692
+ card.dataset.kind = kind;
693
+ const top = el("span", "kind-top");
694
+ top.append(svgIcon(icon), el("b", "", title));
695
+ card.append(top, el("span", "kind-hint", hint), svgIcon(CHECK, "tick"));
696
+ return card;
697
+ }
698
+
626
699
  function svgIcon(nodes: IconNode, className = "icon"): SVGSVGElement {
627
700
  const svg = document.createElementNS(SVG_NS, "svg");
628
701
  for (const [k, v] of Object.entries({ viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", class: className })) svg.setAttribute(k, v);
package/src/ui/strings.ts CHANGED
@@ -74,6 +74,9 @@ export interface Strings {
74
74
  kindProblemHint: string;
75
75
  kindRequest: string;
76
76
  kindRequestHint: string;
77
+ /** On the cards before anything is asked of the browser: what each will do. */
78
+ chooseProblemHint: string;
79
+ chooseRequestHint: string;
77
80
  requestTitle: string;
78
81
  requestTypeLabel: string;
79
82
  /** Carries `{time}`. */
@@ -146,6 +149,8 @@ const th: Strings = {
146
149
  kindProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้",
147
150
  kindRequest: "คำขอ",
148
151
  kindRequestHint: "ขอให้ทีมทำ เช่น ขอสิทธิ์ ขอแก้ข้อมูล",
152
+ chooseProblemHint: "มีอะไรเสีย หรือใช้งานไม่ได้ — แนบภาพหน้าจอ",
153
+ chooseRequestHint: "ขอให้ทีมทำ เช่น ขอสิทธิ์ ขอแก้ข้อมูล — ไม่ต้องแชร์หน้าจอ",
149
154
  requestTitle: "ขอให้ทีมดำเนินการ",
150
155
  requestTypeLabel: "เรื่องที่ขอ",
151
156
  requestTarget: "ทีมตั้งใจให้เสร็จภายใน {time}",
@@ -213,6 +218,8 @@ const en: Strings = {
213
218
  kindProblemHint: "Something is broken or not working",
214
219
  kindRequest: "Request",
215
220
  kindRequestHint: "Ask the team to do something, like access or a data change",
221
+ chooseProblemHint: "Something is broken or not working — with a screenshot",
222
+ chooseRequestHint: "Ask the team to do something, like access or a data change — no screen sharing",
216
223
  requestTitle: "Ask the team",
217
224
  requestTypeLabel: "What you need",
218
225
  requestTarget: "The team aims to finish within {time}",
package/src/ui/styles.ts CHANGED
@@ -99,6 +99,11 @@ canvas { width: 100%; height: auto; display: block; border: 1px solid #e2e8f0; b
99
99
  สร้างเวิร์กโฟลว์ asks it; what is asked for as cards, the way the portal lists it. */
100
100
  [hidden] { display: none !important; }
101
101
  .kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: -4px; }
102
+ /* Before anything is asked of the browser: the same two cards, a little larger, in a smaller dialog. */
103
+ .panel.narrow { width: min(560px, 100%); }
104
+ .kinds.choose { margin: 0 0 24px; }
105
+ .kinds.choose button.kind { padding: 16px 18px; gap: 6px; }
106
+ .kinds.choose .kind-hint { font-size: 13px; }
102
107
  button.kind {
103
108
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
104
109
  padding: 12px 34px 12px 14px; text-align: left; border-radius: 12px;