@lightworkai.official/debug-capture-react 0.6.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.
Files changed (41) hide show
  1. package/README.md +85 -0
  2. package/dist/DebugCaptureInit.d.ts +25 -0
  3. package/dist/DebugErrorBoundary.d.ts +26 -0
  4. package/dist/ReportProblemButton.d.ts +21 -0
  5. package/dist/components/ImageAnnotatorDialog.d.ts +11 -0
  6. package/dist/components/MyTicketsPanel.d.ts +11 -0
  7. package/dist/components/ReopenPanel.d.ts +7 -0
  8. package/dist/components/RichReplyEditor.d.ts +29 -0
  9. package/dist/components/StatusHero.d.ts +8 -0
  10. package/dist/components/StatusPill.d.ts +14 -0
  11. package/dist/components/TicketBody.d.ts +5 -0
  12. package/dist/components/TicketConversation.d.ts +13 -0
  13. package/dist/components/TicketDetail.d.ts +23 -0
  14. package/dist/components/TicketScreenshots.d.ts +6 -0
  15. package/dist/components/TicketTable.d.ts +18 -0
  16. package/dist/components/TicketTimeline.d.ts +14 -0
  17. package/dist/components/ToolbarButton.d.ts +18 -0
  18. package/dist/components/icons.d.ts +35 -0
  19. package/dist/crash.d.ts +5 -0
  20. package/dist/index.d.ts +23 -0
  21. package/dist/index.mjs +1705 -0
  22. package/package.json +52 -0
  23. package/src/DebugCaptureInit.tsx +56 -0
  24. package/src/DebugErrorBoundary.tsx +50 -0
  25. package/src/ReportProblemButton.tsx +79 -0
  26. package/src/components/ImageAnnotatorDialog.tsx +160 -0
  27. package/src/components/MyTicketsPanel.tsx +321 -0
  28. package/src/components/ReopenPanel.tsx +52 -0
  29. package/src/components/RichReplyEditor.tsx +264 -0
  30. package/src/components/StatusHero.tsx +92 -0
  31. package/src/components/StatusPill.tsx +14 -0
  32. package/src/components/TicketBody.tsx +50 -0
  33. package/src/components/TicketConversation.tsx +177 -0
  34. package/src/components/TicketDetail.tsx +110 -0
  35. package/src/components/TicketScreenshots.tsx +41 -0
  36. package/src/components/TicketTable.tsx +161 -0
  37. package/src/components/TicketTimeline.tsx +81 -0
  38. package/src/components/ToolbarButton.tsx +37 -0
  39. package/src/components/icons.tsx +79 -0
  40. package/src/crash.ts +15 -0
  41. package/src/index.ts +27 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The lifecycle, chronologically — including a reopen, which is the event a
3
+ * fixed happy-path progress bar hides and the one a reporter most wants to see
4
+ * recorded.
5
+ */
6
+ import { formatDateTime, type StatusEvent, type StatusMaps } from "@lightworkai.official/debug-capture";
7
+ import { RotateIcon } from "./icons";
8
+
9
+ /** The forward happy path, for the stages still to come. */
10
+ const HAPPY = ["OPEN", "IN_PROGRESS", "RESOLVED", "CLOSED"];
11
+
12
+ interface Node {
13
+ label: string;
14
+ time: string;
15
+ state: "done" | "current" | "pending";
16
+ reopened: boolean;
17
+ }
18
+
19
+ export function TicketTimeline({
20
+ history, status, createdAt, maps, timezone, locale,
21
+ }: {
22
+ history: StatusEvent[];
23
+ status: string;
24
+ createdAt: string;
25
+ maps: StatusMaps;
26
+ timezone: string;
27
+ locale: "th" | "en";
28
+ }) {
29
+ const events = history.length
30
+ ? history.map((h) => ({ status: h.toStatus, at: h.createdAt }))
31
+ : // A row from before history was recorded still gets one node rather than
32
+ // an empty section.
33
+ [{ status: "OPEN", at: createdAt }];
34
+
35
+ const nodes: Node[] = events.map((event, i) => {
36
+ const previous = events[i - 1];
37
+ return {
38
+ label: i === 0 ? (locale === "en" ? "Reported" : "รายงานปัญหา") : maps.labelOf(event.status),
39
+ time: formatDateTime(event.at, timezone, locale),
40
+ state: i === events.length - 1 ? "current" : "done",
41
+ reopened: Boolean(previous && maps.isClosed(previous.status) && !maps.isClosed(event.status)),
42
+ };
43
+ });
44
+
45
+ const index = HAPPY.indexOf(status);
46
+ const upcoming = maps.isClosed(status) || index < 0 ? [] : HAPPY.slice(index + 1);
47
+ for (const next of upcoming) {
48
+ nodes.push({
49
+ label: maps.labelOf(next),
50
+ time: locale === "en" ? "Pending" : "รอดำเนินการ",
51
+ state: "pending",
52
+ reopened: false,
53
+ });
54
+ }
55
+
56
+ return (
57
+ <ol className="lw-timeline">
58
+ {nodes.map((node, i) => (
59
+ <li key={`${node.label}-${i}`} className="lw-node">
60
+ <div className="lw-rail">
61
+ {i < nodes.length - 1 && (
62
+ <span className={`lw-line${node.state === "pending" ? " lw-line--pending" : ""}`} />
63
+ )}
64
+ <span className={`lw-dot lw-dot--${node.state}${node.reopened ? " lw-dot--reopened" : ""}`}>
65
+ {node.state === "current" && !node.reopened && <span className="lw-pip" />}
66
+ </span>
67
+ </div>
68
+ <div className={`lw-node__text${i === nodes.length - 1 ? " lw-node__text--last" : ""}`}>
69
+ <div
70
+ className={`lw-node__label lw-node__label--${node.state}${node.reopened ? " lw-node__label--reopened" : ""}`}
71
+ >
72
+ {node.reopened && <RotateIcon className="lw-node__icon" />}
73
+ {node.reopened ? `${locale === "en" ? "Reopened" : "เปิดใหม่"} · ${node.label}` : node.label}
74
+ </div>
75
+ <div className="lw-node__time">{node.time}</div>
76
+ </div>
77
+ </li>
78
+ ))}
79
+ </ol>
80
+ );
81
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * One editor toolbar button.
3
+ *
4
+ * It fires on `mousedown` with the default prevented, not on `click`, and that
5
+ * is the whole reason it is a component rather than a `<button>` repeated seven
6
+ * times: a toolbar button taking focus collapses the editor's selection, so by
7
+ * the time `click` fires the command has nothing to apply to. Getting that
8
+ * wrong is invisible until someone selects a word and presses B.
9
+ */
10
+ import type { ReactNode } from "react";
11
+
12
+ export interface ToolbarButtonProps {
13
+ label: string;
14
+ active?: boolean;
15
+ disabled?: boolean;
16
+ onActivate: () => void;
17
+ children: ReactNode;
18
+ }
19
+
20
+ export function ToolbarButton({ label, active, disabled, onActivate, children }: ToolbarButtonProps) {
21
+ return (
22
+ <button
23
+ type="button"
24
+ className={`lw-tool${active ? " lw-tool--on" : ""}`}
25
+ title={label}
26
+ aria-label={label}
27
+ aria-pressed={active ? "true" : "false"}
28
+ disabled={disabled}
29
+ onMouseDown={(event) => {
30
+ event.preventDefault();
31
+ onActivate();
32
+ }}
33
+ >
34
+ {children}
35
+ </button>
36
+ );
37
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The panel's icons, drawn rather than imported.
3
+ *
4
+ * The original uses lucide, and a package that pulled an icon set in so a reply
5
+ * box could show a B would cost every consumer that dependency. These are the
6
+ * same lucide paths at the same 24-unit scale.
7
+ */
8
+ import type { SVGProps } from "react";
9
+
10
+ function glyph(paths: string[], size = 15) {
11
+ return function Icon(props: SVGProps<SVGSVGElement>) {
12
+ return (
13
+ <svg
14
+ width={size}
15
+ height={size}
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ strokeWidth={2}
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ aria-hidden="true"
23
+ {...props}
24
+ >
25
+ {paths.map((d) => (
26
+ <path key={d} d={d} />
27
+ ))}
28
+ </svg>
29
+ );
30
+ };
31
+ }
32
+
33
+ export const BoldIcon = glyph(["M6 4h8a4 4 0 0 1 0 8H6z", "M6 12h9a4 4 0 0 1 0 8H6z"]);
34
+ export const ItalicIcon = glyph(["M19 4h-9", "M14 20H5", "M15 4L9 20"]);
35
+ export const UnderlineIcon = glyph(["M6 4v6a6 6 0 0 0 12 0V4", "M4 20h16"]);
36
+ export const BulletListIcon = glyph(["M8 6h13", "M8 12h13", "M8 18h13", "M3 6h.01", "M3 12h.01", "M3 18h.01"]);
37
+ export const OrderedListIcon = glyph(["M10 6h11", "M10 12h11", "M10 18h11", "M4 6h1v4", "M4 10h2", "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"]);
38
+ export const LinkIcon = glyph([
39
+ "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
40
+ "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",
41
+ ]);
42
+ export const ImageIcon = glyph([
43
+ "M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z",
44
+ "M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z",
45
+ "M21 15l-5-5L5 21",
46
+ ]);
47
+ export const SendIcon = glyph(["M22 2L11 13", "M22 2l-7 20-4-9-9-4 20-7z"]);
48
+ export const SearchIcon = glyph(["M21 21l-4.34-4.34", "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z"], 16);
49
+ export const ArrowLeftIcon = glyph(["M19 12H5", "M12 19l-7-7 7-7"], 14);
50
+ export const CheckIcon = glyph(["M20 6L9 17l-5-5"], 12);
51
+ export const MessageIcon = glyph(["M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"], 12);
52
+ export const RotateIcon = glyph(["M3 12a9 9 0 1 0 3-6.7L3 8", "M3 3v5h5"], 14);
53
+ export const AlertIcon = glyph(["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 8v4", "M12 16h.01"], 16);
54
+ export const InboxIcon = glyph([
55
+ "M22 12h-6l-2 3h-4l-2-3H2",
56
+ "M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",
57
+ ], 22);
58
+ export const ClockIcon = glyph(["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 6v6l4 2"], 22);
59
+ export const CheckCircleIcon = glyph(["M22 11.08V12a10 10 0 1 1-5.93-9.14", "M22 4L12 14.01l-3-3"], 22);
60
+ export const BanIcon = glyph(["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M4.93 4.93l14.14 14.14"], 22);
61
+ export const WrenchIcon = glyph([
62
+ "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",
63
+ ], 16);
64
+
65
+ /* The annotator toolbar — the same four tools the app's dialog offers. */
66
+ export const SquareIcon = glyph(["M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z"], 14);
67
+ export const ArrowUpRightIcon = glyph(["M7 17L17 7", "M7 7h10v10"], 14);
68
+ export const HighlighterIcon = glyph([
69
+ "M9 11l-6 6v3h9l3-3",
70
+ "M22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4",
71
+ ], 14);
72
+ export const TypeIcon = glyph(["M4 7V4h16v3", "M9 20h6", "M12 4v16"], 14);
73
+ export const TrashIcon = glyph([
74
+ "M3 6h18",
75
+ "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",
76
+ ], 14);
77
+ export const PencilIcon = glyph(["M12 20h9", "M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"], 11);
78
+ export const CheckSmallIcon = glyph(["M20 6L9 17l-5-5"], 12);
79
+ export const XSmallIcon = glyph(["M18 6L6 18", "M6 6l12 12"], 12);
package/src/crash.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { emitCrash } from "@lightworkai.official/debug-capture";
2
+
3
+ /**
4
+ * Announce a render crash on the same channel as an uncaught error, so a host
5
+ * that already listens for crashes gets these too without a second wiring.
6
+ */
7
+ export function emitCrashFromBoundary(error: Error, componentStack?: string): void {
8
+ emitCrash({
9
+ type: "framework-error",
10
+ message: error.message,
11
+ // The component stack is what makes a React crash locatable; the JS stack
12
+ // usually points into the renderer.
13
+ stack: componentStack ? `${error.stack ?? ""}\n\nComponent stack:${componentStack}` : error.stack,
14
+ });
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * React bindings for @lightworkai.official/debug-capture.
3
+ *
4
+ * Thin on purpose: the dialog, the annotator and the capture all live in the
5
+ * core as one custom element, so these three components are a mount, a click
6
+ * and an error boundary. Everything a Vue or Angular host gets, a React host
7
+ * gets from the same code.
8
+ */
9
+ export { DebugCaptureInit } from "./DebugCaptureInit";
10
+ export type { DebugCaptureInitProps } from "./DebugCaptureInit";
11
+ export { ReportProblemButton } from "./ReportProblemButton";
12
+ export type { ReportProblemButtonProps } from "./ReportProblemButton";
13
+ export { DebugErrorBoundary } from "./DebugErrorBoundary";
14
+ export { MyTicketsPanel } from "./components/MyTicketsPanel";
15
+ export type { MyTicketsPanelProps } from "./components/MyTicketsPanel";
16
+
17
+ // Composable pieces, for a host that wants to arrange them itself.
18
+ export { TicketTable } from "./components/TicketTable";
19
+ export { TicketDetail } from "./components/TicketDetail";
20
+ export { TicketConversation } from "./components/TicketConversation";
21
+ export { RichReplyEditor } from "./components/RichReplyEditor";
22
+ export { TicketBody } from "./components/TicketBody";
23
+ export { ImageAnnotatorDialog } from "./components/ImageAnnotatorDialog";
24
+
25
+ // Re-exported so a host needs one import, not two.
26
+ export { configureDebugCapture, launch, onCrash } from "@lightworkai.official/debug-capture";
27
+ export type { DebugCaptureConfig, DebugReason, ErroredQuery, MyTicketListItem, MyTicketDetail } from "@lightworkai.official/debug-capture";